import {
  waitForLegacySession,
  levelBus,
  LEVEL_EVENT,
  MultiTrackRecorder,
  CloudUploadCoordinator,
  bridgeLegacyMeters,
  monitorTrackLevel,
} from '../core/index.js';

const STUDIO_ROOT_ID = 'podcast-root';
const ROSTER_REFRESH_MS = 1500;
const PREFLIGHT_STORAGE_KEY = 'podcastStudio.preflightState';
const PREFLIGHT_CACHE_MS = 6 * 60 * 60 * 1000;
const PREFLIGHT_MIN_MANDATORY_MS = 5 * 60 * 1000;
const DROPBOX_GUIDE_URL = '/cloud.html#dropbox';
const CLOUD_STATUS_STORAGE_KEY = 'podcastStudio.cloudStatus';
const CLOUD_STATUS_STALE_MS = 30 * 60 * 1000;
const DISK_RECORDING_STORAGE_KEY = 'podcastStudio.diskRecordingState';
const DISK_DB_NAME = 'podcastStudio.disk';
const DISK_DB_STORE = 'handles';
const PODCAST_CLOUD_EVENT = 'podcast-cloud-status';
const PODCAST_DISK_EVENT = 'podcast-disk-state';
const PODCAST_RECORD_PLAN_EVENT = 'podcast-record-plan';
const PODCAST_RECORD_STATUS_EVENT = 'podcast-record-status';
const UPLOAD_TRACKER_COOLDOWN_MS = 15000;
const DRIVE_PROGRESS_EVENT = 'vdoninja:gdrive-progress';
const DRIVE_STATUS_RESET_MS = 8000;
const DRIVE_STATUS_MESSAGES = {
  idle: 'Drive idle',
  pending: 'Drive readying…',
  uploading: 'Drive uploading…',
  done: 'Drive upload complete',
  error: 'Drive upload error',
};
const STUDIO_DISK_FEATURE_FLAG = (() => {
  let enabled = true;
  if (typeof urlParams !== 'undefined' && urlParams) {
    const hasParam = typeof urlParams.has === 'function' ? urlParams.has('studioiso') : false;
    if (hasParam) {
      const rawValue = typeof urlParams.get === 'function' ? urlParams.get('studioiso') : null;
      const normalized = (rawValue || '1').toString().toLowerCase();
      enabled = !['0', 'false', 'off', 'no'].includes(normalized);
    }
  }
  return enabled;
})();

function injectStylesheet() {
  if (document.getElementById('podcast-studio-style')) {
    return;
  }
  const link = document.createElement('link');
  link.id = 'podcast-studio-style';
  link.rel = 'stylesheet';
  link.href = new URL('./studio.css', import.meta.url).toString();
  document.head.appendChild(link);
}

function createElement(tag, className, attrs = {}) {
  const el = document.createElement(tag);
  if (className) {
    el.className = className;
  }
  Object.entries(attrs).forEach(([key, value]) => {
    if (value === undefined || value === null) {
      return;
    }
    if (key === 'text') {
      el.textContent = value;
    } else {
      el.setAttribute(key, value);
    }
  });
  return el;
}

function dispatchStudioEvent(name, detail = {}) {
  if (typeof window === 'undefined' || typeof window.dispatchEvent !== 'function') {
    return;
  }
  try {
    window.dispatchEvent(new CustomEvent(name, { detail }));
  } catch (error) {
    console.warn('Unable to dispatch studio event', name, error);
  }
}

const SPECTROGRAM_GRADIENT = [
  { stop: 0, color: [4, 5, 13] }, // floor
  { stop: 0.25, color: [24, 60, 140] },
  { stop: 0.45, color: [47, 231, 163] }, // studio green accent
  { stop: 0.7, color: [255, 153, 68] }, // warning orange
  { stop: 1, color: [255, 255, 255] },
];

const DEFAULT_SPECTROGRAM_OPTIONS = {
  fps: 24,
  pixelStep: 1,
  decay: 0.008,
  noiseFloor: 2,
  gamma: 0.65,
  frequencyExponent: 0.95,
  lowFrequencyCutoff: 0.55,
  lowFrequencyGain: 1.1,
  lowFrequencySpread: 2,
};

function lerpColorChannel(start, end, ratio) {
  return Math.round(start + (end - start) * ratio);
}

function pickSpectrogramColor(value) {
  const clamped = Math.min(1, Math.max(0, value));
  for (let i = 1; i < SPECTROGRAM_GRADIENT.length; i += 1) {
    const prev = SPECTROGRAM_GRADIENT[i - 1];
    const next = SPECTROGRAM_GRADIENT[i];
    if (clamped <= next.stop) {
      const span = next.stop - prev.stop || 1;
      const ratio = (clamped - prev.stop) / span;
      return [
        lerpColorChannel(prev.color[0], next.color[0], ratio),
        lerpColorChannel(prev.color[1], next.color[1], ratio),
        lerpColorChannel(prev.color[2], next.color[2], ratio),
      ];
    }
  }
  const fallback = SPECTROGRAM_GRADIENT[SPECTROGRAM_GRADIENT.length - 1];
  return [...fallback.color];
}

class SpectrogramRenderer {
  constructor(canvas, options = {}) {
    this.canvas = canvas;
    this.ctx = canvas?.getContext ? canvas.getContext('2d', { alpha: true }) : null;
    this.options = { ...DEFAULT_SPECTROGRAM_OPTIONS, ...options };
    this.pixelStepBase = Math.max(1, this.options.pixelStep);
    this.frameInterval = this.options.fps > 0 ? 1000 / this.options.fps : 0;
    this.lastFrame = 0;
    this.animationFrame = null;
    this.resizeObserver = null;
    this.resizeListener = null;
    this.columnBuffer = null;
    this.analyser = null;
    this.frequencyData = null;
    this.width = 0;
    this.height = 0;
    this.pixelStep = this.pixelStepBase;
    this.noiseFloor = Math.max(0, this.options.noiseFloor);
    this.gamma = Math.max(0.25, Math.min(1.5, this.options.gamma));
    this.frequencyExponent = Math.max(0.4, Math.min(2.4, this.options.frequencyExponent));
    this.lowFrequencyCutoff = Math.min(0.9, Math.max(0.05, this.options.lowFrequencyCutoff || 0.3));
    this.lowFrequencyGain = Math.max(1, this.options.lowFrequencyGain || 1.2);
    this.lowFrequencySpread = Math.max(1, Math.round(this.options.lowFrequencySpread || 2));
    this.baseFillStyle = 'rgb(4, 5, 13)';
    this.boundResize = () => this.handleResize();
    this.renderLoop = (timestamp) => this.tick(timestamp);
    if (this.ctx && this.canvas) {
      this.ctx.imageSmoothingEnabled = false;
      this.observeResize();
      this.handleResize();
    }
  }

  observeResize() {
    if (!this.canvas) {
      return;
    }
    if (typeof ResizeObserver === 'function') {
      this.resizeObserver = new ResizeObserver(this.boundResize);
      this.resizeObserver.observe(this.canvas);
    } else {
      this.resizeListener = this.boundResize;
      window.addEventListener('resize', this.resizeListener);
    }
  }

  handleResize() {
    if (!this.canvas || !this.ctx) {
      return;
    }
    const rect = this.canvas.getBoundingClientRect();
    const dpr = window.devicePixelRatio || 1;
    const nextWidth = Math.max(10, Math.floor(rect.width * dpr) || 10);
    const nextHeight = Math.max(10, Math.floor(rect.height * dpr) || 10);
    if (nextWidth === this.width && nextHeight === this.height) {
      return;
    }
    this.width = nextWidth;
    this.height = nextHeight;
    this.pixelStep = Math.max(1, Math.round(this.pixelStepBase * dpr));
    this.canvas.width = nextWidth;
    this.canvas.height = nextHeight;
    this.columnBuffer = this.ctx.createImageData(this.pixelStep, this.height);
    this.ctx.fillStyle = this.baseFillStyle;
    this.ctx.fillRect(0, 0, this.width, this.height);
  }

  ensureColumnBuffer() {
    if (!this.ctx) {
      return null;
    }
    if (!this.columnBuffer || this.columnBuffer.height !== this.height || this.columnBuffer.width !== this.pixelStep) {
      this.columnBuffer = this.ctx.createImageData(this.pixelStep, this.height);
    }
    return this.columnBuffer;
  }

  normalizeMagnitude(rawValue) {
    if (!Number.isFinite(rawValue)) {
      return 0;
    }
    const adjusted = Math.max(0, rawValue - this.noiseFloor);
    const normalized = Math.min(1, adjusted / (255 - this.noiseFloor));
    return Math.pow(normalized, this.gamma);
  }

  setAnalyser(analyser) {
    if (this.analyser === analyser) {
      return;
    }
    this.analyser = analyser || null;
    this.frequencyData = this.analyser ? new Uint8Array(this.analyser.frequencyBinCount) : null;
    if (this.analyser) {
      this.startLoop();
    } else {
      this.stopLoop();
    }
  }

  startLoop() {
    if (this.animationFrame || !this.analyser) {
      return;
    }
    this.lastFrame = 0;
    this.animationFrame = requestAnimationFrame(this.renderLoop);
  }

  stopLoop() {
    if (this.animationFrame) {
      cancelAnimationFrame(this.animationFrame);
      this.animationFrame = null;
    }
  }

  tick(timestamp) {
    if (!this.analyser || !this.frequencyData || !this.ctx || !this.canvas) {
      this.stopLoop();
      return;
    }
    if (this.frameInterval && timestamp - this.lastFrame < this.frameInterval) {
      this.animationFrame = requestAnimationFrame(this.renderLoop);
      return;
    }
    this.lastFrame = timestamp;
    this.drawColumn();
    this.animationFrame = requestAnimationFrame(this.renderLoop);
  }

  drawColumn() {
    if (!this.analyser || !this.frequencyData || !this.ctx) {
      return;
    }
    try {
      this.analyser.getByteFrequencyData(this.frequencyData);
    } catch (error) {
      console.warn('Spectrogram analyser unavailable', error);
      this.frequencyData = null;
      return;
    }
    const width = this.canvas.width;
    const height = this.canvas.height;
    const shift = Math.min(this.pixelStep, Math.max(1, width - 1));
    if (!width || !height || !shift) {
      return;
    }
    this.ctx.drawImage(this.canvas, shift, 0, width - shift, height, 0, 0, width - shift, height);
    const fadeStrength = Math.max(0, Math.min(1, this.options.decay));
    if (fadeStrength > 0 && width - shift > 0) {
      this.ctx.save();
      this.ctx.globalAlpha = fadeStrength;
      this.ctx.fillStyle = this.baseFillStyle;
      this.ctx.fillRect(0, 0, width - shift, height);
      this.ctx.restore();
    }
    // clear the area reserved for the new samples
    this.ctx.fillStyle = this.baseFillStyle;
    this.ctx.fillRect(width - shift, 0, shift, height);
    const column = this.ensureColumnBuffer();
    if (!column) {
      return;
    }
    const bins = this.frequencyData.length;
    for (let y = 0; y < height; y += 1) {
      const ratio = 1 - y / height;
      const curved = Math.pow(ratio, this.frequencyExponent); // slower exponent keeps low freqs visible
      const baseIndex = Math.max(0, Math.min(bins - 1, Math.floor(curved * (bins - 1))));
      let accumulator = 0;
      let samples = 0;
      const isLowBand = curved <= this.lowFrequencyCutoff;
      const spread = isLowBand ? this.lowFrequencySpread : 1;
      for (let i = 0; i < spread; i += 1) {
        const idx = Math.min(bins - 1, baseIndex + i);
        accumulator += this.frequencyData[idx];
        samples += 1;
      }
      let magnitude = this.normalizeMagnitude(accumulator / Math.max(1, samples));
      if (isLowBand) {
        magnitude = Math.min(1, magnitude * this.lowFrequencyGain);
      }
      const [r, g, b] = pickSpectrogramColor(magnitude);
      const alpha = Math.round(35 + magnitude * 220);
      for (let x = 0; x < shift; x += 1) {
        const offset = (y * shift + x) * 4;
        column.data[offset] = r;
        column.data[offset + 1] = g;
        column.data[offset + 2] = b;
        column.data[offset + 3] = alpha;
      }
    }
    this.ctx.putImageData(column, width - shift, 0);
  }

  destroy() {
    this.stopLoop();
    if (this.resizeObserver) {
      this.resizeObserver.disconnect();
      this.resizeObserver = null;
    }
    if (this.resizeListener) {
      window.removeEventListener('resize', this.resizeListener);
      this.resizeListener = null;
    }
    this.analyser = null;
    this.frequencyData = null;
    if (this.ctx && this.canvas) {
      this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    }
  }
}

const ROOM_QUERY_KEYS = ['room', 'roomid', 'r'];
const DIRECTOR_QUERY_KEYS = ['director', 'dir'];
const ROOM_STATE_STORAGE_KEY = 'podcastStudio.lastRoom';

function sanitizeRoomSlug(value) {
  if (!value) {
    return '';
  }
  const trimmed = String(value).trim();
  if (!trimmed) {
    return '';
  }
  try {
    if (typeof window.sanitizeRoomName === 'function') {
      return window.sanitizeRoomName(trimmed);
    }
  } catch (error) {
    console.warn('sanitizeRoomName unavailable', error);
  }
  return trimmed.replace(/[^a-zA-Z0-9_\-]/g, '').slice(0, 64);
}

function getRoomSlugFromParams(params = new URLSearchParams(window.location.search)) {
  for (const key of DIRECTOR_QUERY_KEYS) {
    if (params.has(key)) {
      const slug = sanitizeRoomSlug(params.get(key));
      if (slug) {
        return slug;
      }
    }
  }
  for (const key of ROOM_QUERY_KEYS) {
    if (params.has(key)) {
      const slug = sanitizeRoomSlug(params.get(key));
      if (slug) {
        return slug;
      }
    }
  }
  return '';
}

function readStoredRoomState() {
  try {
    const raw = window.localStorage.getItem(ROOM_STATE_STORAGE_KEY);
    if (!raw) {
      return {};
    }
    const parsed = JSON.parse(raw);
    if (parsed && typeof parsed === 'object') {
      return {
        room: typeof parsed.room === 'string' ? parsed.room : '',
        password: typeof parsed.password === 'string' ? parsed.password : '',
      };
    }
  } catch (error) {
    console.warn('Unable to read stored room state', error);
  }
  return {};
}

function persistStoredRoomState(state) {
  try {
    window.localStorage.setItem(ROOM_STATE_STORAGE_KEY, JSON.stringify(state || {}));
  } catch (error) {
    console.warn('Unable to store room state', error);
  }
}

function readPreflightState() {
  try {
    const raw = window.localStorage.getItem(PREFLIGHT_STORAGE_KEY);
    if (!raw) {
      return {};
    }
    const parsed = JSON.parse(raw);
    if (parsed && typeof parsed === 'object') {
      return parsed;
    }
  } catch (error) {
    console.warn('Unable to read preflight cache', error);
  }
  return {};
}

function writePreflightState(state) {
  try {
    window.localStorage.setItem(PREFLIGHT_STORAGE_KEY, JSON.stringify(state || {}));
  } catch (error) {
    console.warn('Unable to persist preflight cache', error);
  }
}

function isPreflightFresh(timestamp) {
  if (!timestamp) {
    return false;
  }
  return Date.now() - timestamp < PREFLIGHT_CACHE_MS;
}

function formatRelativeTime(timestamp) {
  if (!timestamp) {
    return '';
  }
  const deltaSeconds = Math.max(0, Math.round((Date.now() - timestamp) / 1000));
  if (deltaSeconds < 45) {
    return 'just now';
  }
  if (deltaSeconds < 90) {
    return 'about a minute ago';
  }
  if (deltaSeconds < 45 * 60) {
    const minutes = Math.round(deltaSeconds / 60);
    return `${minutes} minute${minutes === 1 ? '' : 's'} ago`;
  }
  if (deltaSeconds < 90 * 60) {
    return 'about an hour ago';
  }
  if (deltaSeconds < 36 * 3600) {
    const hours = Math.round(deltaSeconds / 3600);
    return `${hours} hour${hours === 1 ? '' : 's'} ago`;
  }
  const days = Math.round(deltaSeconds / 86400);
  return `${days} day${days === 1 ? '' : 's'} ago`;
}

function createRecordingSessionId() {
  try {
    if (typeof crypto !== 'undefined' && crypto.randomUUID) {
      return crypto.randomUUID();
    }
  } catch (error) {
    console.warn('randomUUID unavailable', error);
  }
  return `rec-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
}

function snapshotHighResClock() {
  if (typeof performance === 'undefined' || typeof performance.now !== 'function') {
    return null;
  }
  const now = performance.now();
  const origin =
    typeof performance.timeOrigin === 'number'
      ? performance.timeOrigin
      : Date.now() - now;
  return {
    perfNow: now,
    timeOrigin: origin,
    wallClockMs: Math.round(origin + now),
  };
}

function readCloudLinkStatus() {
  try {
    const raw = window.localStorage.getItem(CLOUD_STATUS_STORAGE_KEY);
    if (!raw) {
      return {};
    }
    const parsed = JSON.parse(raw);
    return parsed && typeof parsed === 'object' ? parsed : {};
  } catch (error) {
    console.warn('Unable to read cloud link status', error);
    return {};
  }
}

function writeCloudLinkStatus(nextState) {
  const snapshot = nextState || {};
  try {
    window.localStorage.setItem(CLOUD_STATUS_STORAGE_KEY, JSON.stringify(snapshot));
  } catch (error) {
    console.warn('Unable to persist cloud link status', error);
    return;
  }
  dispatchStudioEvent(PODCAST_CLOUD_EVENT, { state: snapshot });
}

function isCloudLinkFresh(entry) {
  if (!entry?.linkedAt) {
    return false;
  }
  return Date.now() - entry.linkedAt < CLOUD_STATUS_STALE_MS;
}

function markCloudLinked(service, details = {}) {
  if (!service) {
    return;
  }
  const state = readCloudLinkStatus();
  state[service] = {
    linkedAt: Date.now(),
    ...details,
  };
  writeCloudLinkStatus(state);
}

function markCloudUnlinked(service) {
  if (!service) {
    return;
  }
  const state = readCloudLinkStatus();
  if (state[service]) {
    delete state[service];
    writeCloudLinkStatus(state);
  }
}

function readDiskRecordingState() {
  try {
    const raw = window.localStorage.getItem(DISK_RECORDING_STORAGE_KEY);
    if (!raw) {
      return {};
    }
    const parsed = JSON.parse(raw);
    return parsed && typeof parsed === 'object' ? parsed : {};
  } catch (error) {
    console.warn('Unable to read disk recording state', error);
    return {};
  }
}

function isDiskRecordingEnabled() {
  const state = readDiskRecordingState();
  return Boolean(state.folderName && state.enabled);
}

function setDiskRecordingEnabled(enabled) {
  const current = readDiskRecordingState();
  const next = {
    ...current,
    enabled: Boolean(enabled) && Boolean(current.folderName),
    updatedAt: Date.now(),
  };
  writeDiskRecordingState(next);
  return next;
}

function writeDiskRecordingState(state) {
  const snapshot = state || {};
  try {
    window.localStorage.setItem(DISK_RECORDING_STORAGE_KEY, JSON.stringify(snapshot));
  } catch (error) {
    console.warn('Unable to persist disk recording state', error);
    return;
  }
  dispatchStudioEvent(PODCAST_DISK_EVENT, { state: snapshot });
}

function openDiskHandleDatabase() {
  return new Promise((resolve, reject) => {
    if (!window.indexedDB) {
      reject(new Error('IndexedDB unavailable'));
      return;
    }
    const request = window.indexedDB.open(DISK_DB_NAME, 1);
    request.onerror = () => reject(request.error || new Error('Unable to open disk handle database'));
    request.onupgradeneeded = () => {
      const db = request.result;
      if (!db.objectStoreNames.contains(DISK_DB_STORE)) {
        db.createObjectStore(DISK_DB_STORE);
      }
    };
    request.onsuccess = () => resolve(request.result);
  });
}

async function saveDiskDirectoryHandle(handle) {
  if (!handle) {
    return;
  }
  const db = await openDiskHandleDatabase();
  await new Promise((resolve, reject) => {
    const tx = db.transaction(DISK_DB_STORE, 'readwrite');
    tx.oncomplete = () => {
      db.close();
      resolve();
    };
    tx.onerror = () => {
      db.close();
      reject(tx.error || new Error('Unable to store disk handle'));
    };
    tx.objectStore(DISK_DB_STORE).put(handle, 'primary');
  });
}

async function readDiskDirectoryHandle() {
  const db = await openDiskHandleDatabase();
  return new Promise((resolve, reject) => {
    const tx = db.transaction(DISK_DB_STORE, 'readonly');
    tx.oncomplete = () => {
      db.close();
    };
    tx.onerror = () => {
      db.close();
      reject(tx.error || new Error('Unable to read disk handle'));
    };
    const request = tx.objectStore(DISK_DB_STORE).get('primary');
    request.onsuccess = () => resolve(request.result || null);
  });
}

async function verifyStoredDiskRecordingDirectory({ requestPermission = false } = {}) {
  try {
    const handle = await readDiskDirectoryHandle();
    if (!handle) {
      return { ok: false, message: 'No folder selected yet.' };
    }
    let permission = await handle.queryPermission({ mode: 'readwrite' });
    if (permission === 'prompt' && requestPermission) {
      permission = await handle.requestPermission({ mode: 'readwrite' });
    }
    if (permission !== 'granted') {
      return { ok: false, message: 'Access to the selected folder was denied.' };
    }
    const meta = readDiskRecordingState();
    writeDiskRecordingState({
      ...meta,
      lastVerifiedAt: Date.now(),
      folderName: meta.folderName || handle.name || 'Selected folder',
      lastError: null,
    });
    return { ok: true, folderName: meta.folderName || handle.name || 'Selected folder' };
  } catch (error) {
    console.warn('Failed to verify disk folder', error);
    const meta = readDiskRecordingState();
    writeDiskRecordingState({
      ...meta,
      lastError: error?.message || 'Unable to verify folder access.',
    });
    return { ok: false, message: error?.message || 'Unable to verify folder access.' };
  }
}

async function chooseDiskRecordingDirectory() {
  if (typeof window.showDirectoryPicker !== 'function') {
    throw new Error('This browser does not support the file-system directory picker yet.');
  }
  const handle = await window.showDirectoryPicker({ mode: 'readwrite' });
  if (!handle) {
    throw new Error('Folder selection was cancelled.');
  }
  await saveDiskDirectoryHandle(handle);
  const meta = readDiskRecordingState();
  writeDiskRecordingState({
    ...meta,
    folderName: handle.name || 'Recording folder',
    lastVerifiedAt: Date.now(),
    lastError: null,
  });
  return { handle, folderName: handle.name || 'Recording folder' };
}

function buildRoomGate(defaults = {}) {
  injectStylesheet();
  document.body.classList.remove('hidden');
  document.body.classList.add('podcast-studio-mode');

  const gate = createElement('div', '', { id: 'podcast-room-gate' });
  const panel = createElement('div', 'podcast-room-gate__panel');
  const title = createElement('h1', 'podcast-room-gate__title', { text: 'Start a Control Room' });
  const subtitle = createElement('p', 'podcast-room-gate__subtitle', {
    text: 'Name your room to invite talent and capture their tracks. This matches the “&director=” link you share with guests.',
  });

  const form = createElement('form', 'podcast-room-gate__form');
  const roomLabel = createElement('label');
  roomLabel.append(createElement('span', '', { text: 'Room name' }));
  const roomInput = createElement('input');
  roomInput.name = 'room';
  roomInput.placeholder = defaults.roomPlaceholder || 'podcast-hq';
  roomInput.autocomplete = 'off';
  roomInput.autocapitalize = 'off';
  roomInput.spellcheck = false;
  if (defaults.room) {
    roomInput.value = defaults.room;
  }
  roomLabel.append(roomInput);

  const passwordLabel = createElement('label');
  passwordLabel.append(createElement('span', '', { text: 'Room password (optional)' }));
  const passwordInput = createElement('input');
  passwordInput.name = 'password';
  passwordInput.placeholder = 'Leave blank to skip';
  passwordInput.type = 'text';
  passwordInput.autocomplete = 'off';
  passwordInput.autocapitalize = 'off';
  passwordInput.spellcheck = false;
  if (defaults.password) {
    passwordInput.value = defaults.password;
  }
  passwordLabel.append(passwordInput);

  const errorNode = createElement('div', 'podcast-room-gate__error');

  const actions = createElement('div', 'podcast-room-gate__actions');
  const cancelButton = createElement('button', 'podcast-room-gate__cancel', { type: 'button', text: 'Back to classic' });
  const submitButton = createElement('button', 'podcast-room-gate__submit', { type: 'submit', text: 'Enter studio' });
  actions.append(submitButton, cancelButton);

  form.append(roomLabel, passwordLabel, errorNode, actions);
  panel.append(title, subtitle, form);
  gate.append(panel);
  document.body.append(gate);

  setTimeout(() => {
    roomInput.focus();
    roomInput.select();
  }, 0);

  return {
    gate,
    form,
    roomInput,
    passwordInput,
    errorNode,
    submitButton,
    cancelButton,
  };
}

async function ensureRoomSelection() {
  const params = new URLSearchParams(window.location.search);
  const existing = getRoomSlugFromParams(params);
  if (existing) {
    injectStylesheet();
    document.body.classList.remove('hidden');
    document.body.classList.add('podcast-studio-mode');
    const preflight = await runPreflightChecklist({ roomSlug: existing });
    if (preflight?.redirect) {
      return preflight;
    }
    return { roomSlug: preflight?.roomSlug || existing };
  }

  const stored = readStoredRoomState();
  const gateElements = buildRoomGate(stored);

  return new Promise((resolve) => {
    function redirectToClassic() {
      const base = window.location.pathname;
      gateElements.cancelButton.disabled = true;
      gateElements.submitButton.disabled = true;
      window.location.href = base || '/';
      resolve({ redirect: true });
    }

    function handleSubmit(event) {
      event.preventDefault();
      const slug = sanitizeRoomSlug(gateElements.roomInput.value);
      if (!slug) {
        gateElements.errorNode.textContent = 'Room name is required.';
        return;
      }
      gateElements.errorNode.textContent = '';
      gateElements.submitButton.disabled = true;
      gateElements.cancelButton.disabled = true;

      const updatedParams = new URLSearchParams(window.location.search);
      updatedParams.set('studio', 'podcast');
      updatedParams.set('director', slug);
      for (const key of DIRECTOR_QUERY_KEYS) {
        if (key !== 'director') {
          updatedParams.delete(key);
        }
      }
      for (const key of ROOM_QUERY_KEYS) {
        updatedParams.delete(key);
      }

      const password = gateElements.passwordInput.value.trim();
      if (password) {
        updatedParams.set('password', password);
      } else {
        updatedParams.delete('password');
      }

      persistStoredRoomState({ room: slug, password });
      window.location.search = updatedParams.toString();
      resolve({ redirect: true });
    }

    gateElements.form.addEventListener('submit', (event) => handleSubmit(event));
    gateElements.cancelButton.addEventListener('click', (event) => {
      event.preventDefault();
      redirectToClassic();
    });
    // Rely on form submit for enter/return handling.
  });
}

function describePreflightStatus(status) {
  switch (status) {
    case 'ready':
      return 'Ready';
    case 'testing':
      return 'Testing…';
    case 'error':
      return 'Needs attention';
    default:
      return 'Pending';
  }
}

function createPreflightRow(label, description, options = {}) {
  const {
    initialStatus = 'pending',
    actionLabel = 'Test',
    showAction = true,
  } = options;
  const row = createElement('div', 'preflight-row');
  row.dataset.status = initialStatus;

  const info = createElement('div', 'preflight-row__info');
  const labelNode = createElement('div', 'preflight-row__label', { text: label });
  const descriptionNode = createElement('div', 'preflight-row__description', { text: description });
  const messageNode = createElement('div', 'preflight-row__message');
  info.append(labelNode, descriptionNode, messageNode);

  const controls = createElement('div', 'preflight-row__controls');
  const statusNode = createElement('span', 'preflight-row__status', { text: describePreflightStatus(initialStatus) });
  controls.append(statusNode);

  let actionButton = null;
  if (showAction) {
    actionButton = createElement('button', 'preflight-row__action', { type: 'button', text: actionLabel });
    controls.append(actionButton);
  }

  row.append(info, controls);
  return {
    row,
    info,
    statusNode,
    messageNode,
    actionButton,
  };
}

function setPreflightRowState(rowParts, status, message = '') {
  if (!rowParts || !rowParts.row) {
    return;
  }
  rowParts.row.dataset.status = status;
  if (rowParts.statusNode) {
    rowParts.statusNode.textContent = describePreflightStatus(status);
  }
  if (rowParts.messageNode) {
    rowParts.messageNode.textContent = message || '';
  }
  if (rowParts.actionButton) {
    if (status === 'testing') {
      rowParts.actionButton.disabled = true;
    } else {
      rowParts.actionButton.disabled = false;
    }
    if (status === 'ready') {
      rowParts.actionButton.textContent = 'Retest';
    } else if (status === 'testing') {
      rowParts.actionButton.textContent = 'Testing…';
    } else if (status === 'error') {
      rowParts.actionButton.textContent = 'Retry';
    } else {
      rowParts.actionButton.textContent = rowParts.actionButton.dataset.initialLabel || 'Test';
    }
  }
}

async function runPreflightChecklist({ roomSlug } = {}) {
  const stored = readPreflightState();
  const now = Date.now();
  const micFresh = isPreflightFresh(stored.micSuccessAt);
  const camFresh = isPreflightFresh(stored.cameraSuccessAt);

  // If the user just completed the preflight moments ago, allow immediate pass-through.
  if (stored.completedAt && now - stored.completedAt < PREFLIGHT_MIN_MANDATORY_MS) {
    return { roomSlug, skipped: true };
  }

  const overlay = createElement('div', 'podcast-preflight-backdrop');
  overlay.dataset.podcastOverlay = 'true';
  const panel = createElement('div', 'podcast-preflight-panel');
  panel.setAttribute('role', 'dialog');
  panel.setAttribute('aria-modal', 'true');
  panel.setAttribute('aria-label', 'Podcast studio preflight checklist');

  const heading = createElement('h2', 'preflight-title', { text: 'Check Your Setup' });
  const subtitleText = roomSlug
    ? `Confirm your gear before directing room “${roomSlug}”.`
    : 'Confirm your gear before directing a session.';
  const subtitle = createElement('p', 'preflight-subtitle', { text: subtitleText });

  const checklist = createElement('div', 'preflight-list');
  const micRow = createPreflightRow('Microphone access', 'Verify your preferred mic is available and browser permission is granted.', {
    initialStatus: micFresh ? 'ready' : 'pending',
    actionLabel: micFresh ? 'Retest' : 'Test mic',
  });
  if (micRow.actionButton) {
    micRow.actionButton.dataset.initialLabel = micFresh ? 'Retest' : 'Test mic';
  }
  if (micFresh) {
    setPreflightRowState(micRow, 'ready', `Last checked ${formatRelativeTime(stored.micSuccessAt)}.`);
  }

  const camRow = createPreflightRow('Camera access', 'Optional but useful if you plan to capture video.', {
    initialStatus: camFresh ? 'ready' : 'pending',
    actionLabel: camFresh ? 'Retest' : 'Test camera',
  });
  if (camRow.actionButton) {
    camRow.actionButton.dataset.initialLabel = camFresh ? 'Retest' : 'Test camera';
  }
  if (camFresh) {
    setPreflightRowState(camRow, 'ready', `Last checked ${formatRelativeTime(stored.cameraSuccessAt)}.`);
  }

  const cloudRow = createPreflightRow('Cloud sync', 'Link Google Drive or Dropbox before recording so uploads can start immediately.', {
    initialStatus: 'pending',
    showAction: true,
    actionLabel: 'Check status',
  });
  if (cloudRow.actionButton) {
    cloudRow.actionButton.dataset.initialLabel = 'Check status';
  }
  setPreflightRowState(cloudRow, 'pending', 'Checking saved tokens…');

  const diskRow = createPreflightRow(
    'Local disk recording',
    'Select a destination folder for ISO files (optional but recommended).',
    {
      initialStatus: 'pending',
      showAction: Boolean(window.showDirectoryPicker),
      actionLabel: window.showDirectoryPicker ? 'Choose folder' : 'Unavailable',
    }
  );
  if (!window.showDirectoryPicker && diskRow.actionButton) {
    diskRow.actionButton.disabled = true;
  }
  setPreflightRowState(
    diskRow,
    window.showDirectoryPicker ? 'pending' : 'error',
    window.showDirectoryPicker ? 'No folder selected yet.' : 'Local disk recording requires the File System Access API (Chromium-based browsers).'
  );

  checklist.append(micRow.row, camRow.row, cloudRow.row, diskRow.row);

  const actions = createElement('div', 'preflight-actions');
  const continueButton = createElement('button', 'preflight-primary', { type: 'button', text: 'Enter Control Room' });
  const skipButton = createElement('button', 'preflight-secondary', { type: 'button', text: 'Skip preflight' });
  actions.append(continueButton, skipButton);

  panel.append(heading, subtitle, checklist, actions);
  overlay.append(panel);
  document.body.append(overlay);

  let micOk = Boolean(micFresh);
  let camOk = Boolean(camFresh);
  let cloudTimer = null;
  let cloudOk = false;
  let diskReady = false;
  let destroyed = false;
  let cloudStatusListener = null;
  let diskStatusListener = null;
  let cloudLastChecked = null;
  let resolver;
  const completion = new Promise((resolve) => {
    resolver = resolve;
  });

  function closeOverlay(result = {}) {
    if (destroyed) {
      return;
    }
    destroyed = true;
    if (cloudTimer) {
      clearInterval(cloudTimer);
      cloudTimer = null;
    }
    if (cloudStatusListener) {
      window.removeEventListener(PODCAST_CLOUD_EVENT, cloudStatusListener);
      cloudStatusListener = null;
    }
    if (diskStatusListener) {
      window.removeEventListener(PODCAST_DISK_EVENT, diskStatusListener);
      diskStatusListener = null;
    }
    if (overlay && overlay.parentNode) {
      overlay.parentNode.removeChild(overlay);
    }
    const payload = { roomSlug, ...result };
    if (result.completed) {
      writePreflightState({
        ...stored,
        completedAt: Date.now(),
        micSuccessAt: micOk ? (stored.micSuccessAt || Date.now()) : stored.micSuccessAt,
        cameraSuccessAt: camOk ? (stored.cameraSuccessAt || Date.now()) : stored.cameraSuccessAt,
        roomSlug,
      });
    } else {
      writePreflightState({
        ...stored,
        micSuccessAt: micOk ? (stored.micSuccessAt || Date.now()) : stored.micSuccessAt,
        cameraSuccessAt: camOk ? (stored.cameraSuccessAt || Date.now()) : stored.cameraSuccessAt,
        roomSlug,
      });
    }
    if (typeof resolver === 'function') {
      resolver(payload);
      resolver = null;
    }
  }

  function updateContinueState() {
    continueButton.disabled = !micOk;
    continueButton.title = micOk ? '' : 'Run the microphone test to continue.';
  }

  updateContinueState();

  async function runMediaTest(kind) {
    if (!navigator.mediaDevices || typeof navigator.mediaDevices.getUserMedia !== 'function') {
      throw new Error('Browser does not support media tests.');
    }
    const constraints = kind === 'video' ? { video: true } : { audio: { echoCancellation: false } };
    const stream = await navigator.mediaDevices.getUserMedia(constraints);
    stream.getTracks().forEach((track) => {
      try {
        track.stop();
      } catch (error) {
        console.warn('Unable to stop track', error);
      }
    });
  }

  if (micRow.actionButton) {
    micRow.actionButton.addEventListener('click', async () => {
      setPreflightRowState(micRow, 'testing', 'Requesting microphone access…');
      try {
        await runMediaTest('audio');
        micOk = true;
        const timestamp = Date.now();
        stored.micSuccessAt = timestamp;
        setPreflightRowState(micRow, 'ready', 'Microphone ready to record.');
      } catch (error) {
        console.error('Microphone check failed', error);
        micOk = false;
        setPreflightRowState(
          micRow,
          'error',
          error?.message ? error.message : 'Unable to access microphone.'
        );
      }
      updateContinueState();
      writePreflightState({ ...stored, micSuccessAt: micOk ? Date.now() : stored.micSuccessAt, roomSlug });
    });
  }

  if (camRow.actionButton) {
    camRow.actionButton.addEventListener('click', async () => {
      setPreflightRowState(camRow, 'testing', 'Requesting camera access…');
      try {
        await runMediaTest('video');
        camOk = true;
        const timestamp = Date.now();
        stored.cameraSuccessAt = timestamp;
        setPreflightRowState(camRow, 'ready', 'Camera detected.');
        writePreflightState({ ...stored, cameraSuccessAt: timestamp, roomSlug });
      } catch (error) {
        console.error('Camera check failed', error);
        camOk = false;
        setPreflightRowState(
          camRow,
          'error',
          error?.message ? error.message : 'Unable to access camera.'
        );
        writePreflightState({ ...stored, roomSlug });
      }
    });
  }

  async function refreshCloudRowStatus({ manual = false } = {}) {
    cloudLastChecked = Date.now();
    const state = readCloudLinkStatus();
    const driveFresh = isCloudLinkFresh(state.drive);
    const dropboxFresh = isCloudLinkFresh(state.dropbox);
    const summaries = [];
    summaries.push(
      driveFresh
        ? `Drive linked ${formatRelativeTime(state.drive.linkedAt) || 'recently'}`
        : 'Drive not linked',
    );
    summaries.push(
      dropboxFresh
        ? `Dropbox linked ${formatRelativeTime(state.dropbox.linkedAt) || 'recently'}`
        : 'Dropbox not linked',
    );
    cloudOk = driveFresh || dropboxFresh;
    const summaryText = summaries.join(' • ');
    const checkedSuffix = cloudLastChecked ? `Checked ${formatRelativeTime(cloudLastChecked)}.` : '';
    if (cloudOk) {
      setPreflightRowState(
        cloudRow,
        'ready',
        `${summaryText}.${checkedSuffix ? ` ${checkedSuffix}` : ''}`,
      );
    } else {
      setPreflightRowState(
        cloudRow,
        'error',
        `${summaryText}. Link your cloud targets from inside the control room.${
          checkedSuffix ? ` ${checkedSuffix}` : ''
        }`,
      );
    }
  }

  async function refreshDiskRowStatus({ interactive = false } = {}) {
    if (typeof window.showDirectoryPicker !== 'function') {
      diskReady = false;
      setPreflightRowState(
        diskRow,
        'error',
        'Local disk recording requires a Chromium-based browser with the File System Access API.'
      );
      if (diskRow.actionButton) {
        diskRow.actionButton.disabled = true;
      }
      return;
    }
    const diskState = readDiskRecordingState();
    if (!diskState.folderName) {
      diskReady = false;
      if (diskRow.actionButton) {
        diskRow.actionButton.textContent = 'Choose folder';
        diskRow.actionButton.disabled = false;
      }
      setPreflightRowState(diskRow, 'pending', 'Pick a folder to enable local ISO recording.');
      return;
    }
    setPreflightRowState(diskRow, 'testing', 'Validating folder permissions…');
    const result = await verifyStoredDiskRecordingDirectory({ requestPermission: interactive });
    if (result.ok) {
      diskReady = true;
      const meta = readDiskRecordingState();
      if (diskRow.actionButton) {
        diskRow.actionButton.textContent = 'Change folder';
        diskRow.actionButton.disabled = false;
      }
      const checked = meta.lastVerifiedAt ? `Last checked ${formatRelativeTime(meta.lastVerifiedAt)}.` : 'Ready to write.';
      setPreflightRowState(diskRow, 'ready', `Folder: ${result.folderName}. ${checked}`);
    } else {
      diskReady = false;
      if (diskRow.actionButton) {
        diskRow.actionButton.textContent = 'Choose folder';
        diskRow.actionButton.disabled = false;
      }
      setPreflightRowState(diskRow, 'error', result.message || 'Unable to access the selected folder.');
    }
  }

  refreshCloudRowStatus();
  refreshDiskRowStatus();
  cloudTimer = setInterval(() => {
    refreshCloudRowStatus({ manual: false });
    refreshDiskRowStatus();
  }, 8000);
  cloudStatusListener = () => refreshCloudRowStatus();
  diskStatusListener = () => refreshDiskRowStatus();
  window.addEventListener(PODCAST_CLOUD_EVENT, cloudStatusListener);
  window.addEventListener(PODCAST_DISK_EVENT, diskStatusListener);

  if (cloudRow.actionButton) {
    cloudRow.actionButton.addEventListener('click', () => {
      setPreflightRowState(cloudRow, 'testing', 'Re-checking your saved cloud tokens…');
      refreshCloudRowStatus({ manual: true });
    });
  }

  if (diskRow.actionButton) {
    diskRow.actionButton.addEventListener('click', async () => {
      if (diskRow.actionButton.disabled) {
        return;
      }
      try {
        setPreflightRowState(diskRow, 'testing', 'Waiting for folder selection…');
        await chooseDiskRecordingDirectory();
        await refreshDiskRowStatus({ interactive: true });
      } catch (error) {
        diskReady = false;
        const message =
          error?.name === 'AbortError' || /cancel/i.test(error?.message || '')
            ? 'Folder selection cancelled.'
            : error?.message || 'Unable to choose folder.';
        setPreflightRowState(diskRow, 'error', message);
      }
    });
  }

  continueButton.addEventListener('click', () => {
    if (!micOk) {
      setPreflightRowState(micRow, 'error', 'Microphone test is required before entering.');
      return;
    }
    closeOverlay({ completed: true });
  });

  skipButton.addEventListener('click', () => {
    closeOverlay({ skipped: true });
  });

  overlay.addEventListener('click', (event) => {
    if (event.target === overlay) {
      closeOverlay({ skipped: true });
    }
  });

  document.addEventListener(
    'keydown',
    (event) => {
      if (destroyed) {
        return;
      }
      if (event.key === 'Escape') {
        event.preventDefault();
        closeOverlay({ skipped: true });
      }
    },
    { once: true }
  );

  return completion;
}

function extractPeerAudioStats(peer) {
  const stats = peer?.stats || {};
  const candidates = [
    stats.audio_bitrate_kbps,
    stats.inbound_audio_bitrate_kbps,
    stats.total_audio_bitrate_kbps,
    stats.total_sending_bitrate_kbps,
  ];
  const audioBitrateKbps = candidates.find((value) => typeof value === 'number' && value >= 0);
  const codec =
    typeof stats.audio_codec === 'string'
      ? stats.audio_codec
      : typeof stats.audio_codec_in === 'string'
        ? stats.audio_codec_in
        : typeof stats.audio_codec_out === 'string'
          ? stats.audio_codec_out
          : '';
  return {
    audioBitrateKbps: audioBitrateKbps ?? null,
    audioCodec: codec || null,
  };
}

function collectParticipants(session) {
  const participants = [];

  Object.entries(session.rpcs || {}).forEach(([uuid, peer]) => {
    if (!peer) {
      return;
    }
    const audioTracks = peer.streamSrc?.getAudioTracks?.() || [];
    let level = 0;
    if (peer.stats && typeof peer.stats.Audio_Loudness === 'number') {
      level = peer.stats.Audio_Loudness;
    } else if (peer.audioMeter) {
      level = peer.audioMeter.level || 0;
    }
    const { audioBitrateKbps, audioCodec } = extractPeerAudioStats(peer);
    participants.push({
      uuid,
      label: peer.label || peer.streamID || `Guest ${uuid.substring(0, 4)}`,
      streamID: peer.streamID,
      status: (peer.streamSrc && audioTracks.length) ? 'connected' : 'connecting',
      audioLevel: level,
      isLocal: false,
      role: 'remote',
      audioBitrateKbps,
      audioCodec,
    });
  });

  return participants;
}

class PodcastStudioApp {
  constructor(options = {}) {
    this.options = options || {};
    this.roomHint = this.options.roomHint || '';
    this.session = null;
    this.cloud = null;
    this.recorder = null;
    this.audioContext = null;
    this.recording = false;
    this.rosterItems = new Map();
    this.rosterDriveButtons = new Map();
    this.rosterDriveStatuses = new Map();
    this.driveStatusResetTimers = new Map();
    this.driveProgressSnapshots = new Map();
    this.meterValues = new Map();
    this.outputIndicators = new Map();
    this.trackRuntimeStats = new Map();
    this.trackLevelNodes = new Map();
    this.spectrograms = new Map();
    this.participantMetrics = new Map();
    this.markers = [];
    this.rosterTimer = null;
    this.pendingDriveUploads = [];
    this.levelOff = null;
    this.recordStartedAt = null;
    this.driveStatusNode = null;
    this.dropboxStatusNode = null;
    this.abortUploadsController = null;
    this.activeDownloadUrls = [];
    this.stopMeterBridge = null;
    this.roomName = this.roomHint || '';
    this.virtualParticipants = new Map();
    this.hostMic = null;
    this.hostMicMeter = null;
    this.hostMicButton = null;
    this.hostMicStatusNode = null;
    this.hostMicErrorNode = null;
    this.hostMicBusy = false;
    this.cloudBusy = {
      drive: false,
      dropbox: false,
    };
    this.cloudLinkButtons = {
      drive: null,
      dropbox: null,
    };
    this.cloudLinkStatusNodes = {
      drive: null,
      dropbox: null,
    };
    this.cloudLinkMessages = {
      drive: null,
      dropbox: null,
    };
    this.cloudLinkMessageTextNodes = {
      drive: null,
      dropbox: null,
    };
    this.dropboxTokenInput = null;
    this.dropboxTokenRow = null;
    this.dropboxGuideRow = null;
    this.inviteLinkInput = null;
    this.inviteCopyButton = null;
    this.inviteStatusNode = null;
    this.inviteOptionNodes = {};
    this.inviteCopyTimer = null;
    this.remoteOverlay = null;
    this.remoteOverlayContent = null;
    this.remoteControlState = {
      activeUuid: null,
      element: null,
      placeholder: null,
      wrapper: null,
    };
    this.cloudProgressNodes = {
      drive: null,
      dropbox: null,
    };
    this.uploadTrackers = {
      drive: new Map(),
      dropbox: new Map(),
    };
    this.chatModule = null;
    this.chatPlaceholder = null;
    this.chatPanel = null;
    this.chatCollapseButton = null;
    this.chatPopoutButton = null;
    this.chatCollapsed = false;
    this.chatPopoutAnchor = null;
    this.chatCollapsedHint = null;
    this.diskControls = null;
    this.diskToggleButton = null;
    this.diskFolderButton = null;
    this.diskStatusNode = null;
    this.diskRecordingEnabled = isDiskRecordingEnabled();
    this.diskStateListener = null;
    this.cloudStateListener = null;
    this.cloudSummaryNode = null;
    this.diskSummaryNode = null;
    this.recordingStatusNode = null;
    this.recordingPlan = null;
    this.recordingSessionId = null;
    this.boundDriveProgressHandler = null;
  }

  async init() {
    document.body.classList.remove('hidden');
    document.body.classList.add('podcast-studio-mode');
    injectStylesheet();

    this.session = await waitForLegacySession({ timeoutMs: 15000 });
    this.applyDirectorAudioDefaults();
    this.audioContext = this.session.audioCtx || this.session.audioCtxOutbound || this.createAudioContext();
    this.recorder = new MultiTrackRecorder({
      audioContext: this.audioContext,
      includeVideo: false,
      includeScreenshares: false,
      monitorLevels: true,
      timeslice: 1000,
    });
    this.cloud = new CloudUploadCoordinator(this.session);

    this.roomName = this.resolveRoomName();
    this.buildLayout();
    if (STUDIO_DISK_FEATURE_FLAG) {
      this.diskStateListener = () => {
        this.updateDiskRecordingUI();
        this.updateReadinessSummary();
      };
      window.addEventListener(PODCAST_DISK_EVENT, this.diskStateListener);
    }
    this.cloudStateListener = () => this.updateReadinessSummary();
    window.addEventListener(PODCAST_CLOUD_EVENT, this.cloudStateListener);
    this.boundDriveProgressHandler = (event) => this.handleDriveProgressEvent(event);
    window.addEventListener(DRIVE_PROGRESS_EVENT, this.boundDriveProgressHandler);
    this.updateRoomIndicator();
    this.updateCloudFooter();
    this.attachRecorderEvents();
    this.refreshRoster();
    this.startRosterLoop();
    this.levelOff = levelBus.on(LEVEL_EVENT, (payload) => this.updateMeterFromBus(payload));
    try {
      this.stopMeterBridge = await bridgeLegacyMeters();
    } catch (error) {
      console.warn('Failed to bridge legacy meter events', error);
    }
  }

  resolveRoomName() {
    if (this.session?.roomid && this.session.roomid !== true) {
      return sanitizeRoomSlug(this.session.roomid);
    }
    if (this.session?.director && this.session.director !== true) {
      return sanitizeRoomSlug(this.session.director);
    }
    if (this.roomHint) {
      return sanitizeRoomSlug(this.roomHint);
    }
    const paramsSlug = getRoomSlugFromParams();
    if (paramsSlug) {
      return paramsSlug;
    }
    return '';
  }

  updateRoomIndicator() {
    const latest = this.resolveRoomName();
    if (latest !== this.roomName) {
      this.roomName = latest;
      if (this.sessionInfo) {
        this.sessionInfo.textContent = 'Room: ' + (this.roomName || '—');
      }
      if (this.roomName) {
        const stored = readStoredRoomState();
        persistStoredRoomState({ room: this.roomName, password: stored?.password || '' });
      }
    } else if (!this.roomName && this.sessionInfo) {
      this.sessionInfo.textContent = 'Room: —';
    }
    this.updateInviteLink();
  }

  describeInviteOptions() {
    const labels = [];
    if (this.inviteOptionNodes.proAudio?.checked) {
      labels.push('Pro audio');
    }
    if (this.inviteOptionNodes.disableAec?.checked) {
      labels.push('AEC off');
    }
    if (this.inviteOptionNodes.disableDenoise?.checked) {
      labels.push('Denoise off');
    }
    if (this.inviteOptionNodes.disableAgc?.checked) {
      labels.push('AGC off');
    }
    return labels.join(' • ');
  }

  applyDirectorAudioDefaults() {
    if (!this.session) {
      return;
    }
    if (this.session.stereo === undefined || this.session.stereo === null || this.session.stereo === false || this.session.stereo === 0) {
      this.session.stereo = 1;
    }
    if (!this.session.audiobitrate || this.session.audiobitrate < 192) {
      this.session.audiobitrate = 256;
    }
    if (!this.session.outboundAudioBitrate || this.session.outboundAudioBitrate < 192) {
      this.session.outboundAudioBitrate = 256;
    }
    if (typeof this.session.autoGainControl === 'undefined') {
      this.session.autoGainControl = false;
    }
    if (typeof this.session.noiseSuppression === 'undefined') {
      this.session.noiseSuppression = false;
    }
    if (typeof this.session.echoCancellation === 'undefined') {
      this.session.echoCancellation = false;
    }
    if (typeof this.session.applyStereoDefaults === 'function') {
      try {
        this.session.applyStereoDefaults();
      } catch (error) {
        console.warn('applyStereoDefaults failed', error);
      }
    }
  }

  updateInviteLink() {
    if (!this.inviteLinkInput) {
      return;
    }
    const room = this.resolveRoomName();
    if (!room) {
      this.inviteLinkInput.value = 'Set a room name to generate a guest link';
      this.inviteLinkInput.dataset.state = 'placeholder';
      if (this.inviteCopyButton) {
        this.inviteCopyButton.disabled = true;
      }
      if (this.inviteStatusNode) {
        this.inviteStatusNode.textContent = '';
      }
      return;
    }
    this.inviteLinkInput.dataset.state = 'ready';
    if (this.inviteCopyButton) {
      this.inviteCopyButton.disabled = false;
    }
    const guestUrl = new URL(window.location.href);
    guestUrl.search = '';
    guestUrl.hash = '';

    const params = new URLSearchParams();
    params.set('room', room);
    params.set('miconly', '1');
    params.set('style', '2');
    params.set('showlabel', '1');
    params.set('tips', '1');
    params.set('label', '');

    const options = this.inviteOptionNodes || {};
    const summary = [];
    summary.push('Mic only');
    summary.push('Label prompt');
    summary.push('Name tag overlay');
    summary.push('Join tips');

    if (options.proAudio?.checked) {
      params.set('proaudio', '1');
      params.set('stereo', '1');
      params.set('audiobitrate', '256');
      summary.push('Pro audio');
    } else {
      params.delete('proaudio');
      params.delete('stereo');
      params.delete('audiobitrate');
    }

    if (options.disableAec?.checked) {
      params.set('aec', '0');
      params.set('echocancellation', '0');
      summary.push('AEC off');
    } else {
      params.delete('aec');
      params.delete('echocancellation');
    }

    if (options.disableDenoise?.checked) {
      params.set('denoise', '0');
      summary.push('Denoise off');
    } else {
      params.delete('denoise');
    }

    if (options.disableAgc?.checked) {
      params.set('agc', '0');
      params.set('autogain', '0');
      summary.push('AGC off');
    } else {
      params.delete('agc');
      params.delete('autogain');
    }

    guestUrl.search = params.toString();
    const value = guestUrl.toString();
    this.inviteLinkInput.value = value;
    if (this.inviteStatusNode) {
      this.inviteStatusNode.textContent = summary.length ? summary.join(' • ') : 'Default settings';
    }
  }

  async copyInviteLink() {
    if (!this.inviteLinkInput || this.inviteLinkInput.dataset.state === 'placeholder') {
      return;
    }
    const value = this.inviteLinkInput.value;
    if (!value) {
      return;
    }
    const notify = (message, variant = 'info') => {
      if (!this.inviteStatusNode) {
        return;
      }
      this.inviteStatusNode.textContent = message;
      this.inviteStatusNode.dataset.variant = variant;
      if (this.inviteCopyTimer) {
        clearTimeout(this.inviteCopyTimer);
      }
      this.inviteCopyTimer = setTimeout(() => {
        this.inviteStatusNode.dataset.variant = '';
        this.updateInviteLink();
      }, 3500);
    };
    try {
      if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
        await navigator.clipboard.writeText(value);
        notify('Guest link copied.', 'success');
        return;
      }
    } catch (error) {
      console.warn('Navigator clipboard copy failed', error);
    }
    try {
      this.inviteLinkInput.focus();
      this.inviteLinkInput.select();
      const success = document.execCommand('copy');
      if (success) {
        notify('Guest link copied.', 'success');
      } else {
        notify('Select and copy the link manually.', 'warning');
      }
    } catch (error) {
      console.warn('Fallback copy failed', error);
      notify('Select and copy the link manually.', 'warning');
    }
  }

  getAdditionalRecordingParticipants() {
    const extras = [];
    this.virtualParticipants.forEach((participant) => {
      if (participant && participant.stream) {
        extras.push({
          ...participant,
        });
      }
    });
    return extras;
  }

  async ensureAudioContextResumed() {
    if (!this.audioContext) {
      this.audioContext = this.createAudioContext();
    }
    if (this.audioContext && typeof this.audioContext.resume === 'function' && this.audioContext.state === 'suspended') {
      try {
        await this.audioContext.resume();
      } catch (error) {
        console.warn('Failed to resume audio context', error);
      }
    }
  }

  setHostMicError(message) {
    if (this.hostMicErrorNode) {
      this.hostMicErrorNode.textContent = message || '';
    }
  }

  updateHostMicUI() {
    if (this.hostMicButton) {
      if (this.hostMicBusy || this.recording) {
        this.hostMicButton.disabled = true;
        const busyLabel = this.hostMic?.active ? 'Disabling…' : 'Enabling…';
        this.hostMicButton.textContent = this.recording ? 'Host mic locked during record' : busyLabel;
      } else {
        this.hostMicButton.disabled = false;
        this.hostMicButton.textContent = this.hostMic?.active ? 'Disable Host Mic' : 'Enable Host Mic';
      }
      if (this.hostMic?.active) {
        this.hostMicButton.classList.add('active');
      } else {
        this.hostMicButton.classList.remove('active');
      }
    }
    if (this.hostMicStatusNode) {
      if (this.hostMic?.active) {
        this.hostMicStatusNode.textContent = 'Microphone live';
        this.hostMicStatusNode.dataset.state = 'active';
      } else {
        this.hostMicStatusNode.textContent = 'Microphone idle';
        this.hostMicStatusNode.dataset.state = 'idle';
      }
    }
  }

  async handleHostMicToggle() {
    if (this.recording) {
      this.setHostMicError('Stop the recording to change the host mic.');
      return;
    }
    if (this.hostMicBusy) {
      return;
    }
    if (this.hostMic?.active) {
      await this.disableHostMic();
    } else {
      await this.enableHostMic();
    }
  }

  async enableHostMic() {
    if (this.hostMic?.active) {
      this.updateHostMicUI();
      return;
    }
    if (!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)) {
      this.setHostMicError('Browser does not support microphone capture.');
      return;
    }
    this.hostMicBusy = true;
    this.setHostMicError('');
    this.updateHostMicUI();
    try {
      await this.ensureAudioContextResumed();
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
      const [track] = stream.getAudioTracks();
      if (!track) {
        throw new Error('No audio track available.');
      }
      const label = this.session?.label ? `${this.session.label} (Host)` : 'Host Mic';
      const participant = {
        uuid: 'host-mic',
        label,
        stream,
        streamID: 'host-mic',
        status: 'connected',
        audioLevel: 0,
        isLocal: true,
        kind: 'local',
        role: 'host-mic',
      };
      track.addEventListener('ended', () => {
        if (this.hostMic?.track === track) {
          this.disableHostMic();
        }
      });
      this.hostMic = {
        active: true,
        stream,
        track,
        uuid: participant.uuid,
        label: participant.label,
        streamID: participant.streamID,
        participant,
      };
      this.virtualParticipants.set(participant.uuid, participant);
      if (this.audioContext && track) {
        try {
          this.hostMicMeter = await monitorTrackLevel(this.audioContext, track, {
            uuid: participant.uuid,
            trackType: 'audio',
            metadata: { label: participant.label, source: 'host' },
          });
        } catch (error) {
          console.warn('Failed to attach host mic meter', error);
        }
      }
      this.updateHostMicUI();
      this.refreshRoster();
    } catch (error) {
      console.error('Failed to enable host microphone', error);
      this.setHostMicError(error?.message || 'Unable to access microphone.');
      if (this.hostMic?.stream) {
        try {
          this.hostMic.stream.getTracks().forEach((mediaTrack) => mediaTrack.stop());
        } catch (stopError) {
          console.warn('Failed to stop host mic stream after error', stopError);
        }
      }
      this.hostMic = null;
      this.virtualParticipants.delete('host-mic');
      this.updateHostMicUI();
    } finally {
      this.hostMicBusy = false;
      this.updateHostMicUI();
    }
  }

  async disableHostMic() {
    if (!this.hostMic?.active && !this.virtualParticipants.has('host-mic')) {
      this.hostMic = null;
      this.updateHostMicUI();
      return;
    }
    this.hostMicBusy = true;
    this.updateHostMicUI();
    try {
      if (this.hostMicMeter) {
        try {
          this.hostMicMeter.disconnect({ stopTrack: false });
        } catch (error) {
          console.warn('Failed to disconnect host mic meter', error);
        }
        this.hostMicMeter = null;
      }
      if (this.hostMic?.stream) {
        this.hostMic.stream.getTracks().forEach((track) => {
          try {
            track.stop();
          } catch (error) {
            console.warn('Failed to stop host mic track', error);
          }
        });
      }
    } finally {
      this.virtualParticipants.delete('host-mic');
      this.hostMic = null;
      this.hostMicBusy = false;
      this.setHostMicError('');
      this.updateHostMicUI();
      this.applyMeterValue('host-mic', 0);
      this.refreshRoster();
    }
  }

  setCloudMessage(service, message, variant = 'info') {
    const container = this.cloudLinkMessages?.[service];
    if (!container) {
      return;
    }
    const target = this.cloudLinkMessageTextNodes?.[service] || container;
    target.textContent = message || '';
    container.dataset.variant = message ? variant : '';
  }

  updateCloudLinkUI() {
    const driveLinked = this.cloud?.hasDriveAccess();
    const dropboxLinked = this.cloud?.hasDropboxAccess();
    const cachedState = readCloudLinkStatus();
    if (!driveLinked && cachedState.drive && !isCloudLinkFresh(cachedState.drive)) {
      markCloudUnlinked('drive');
    }
    if (!dropboxLinked && cachedState.dropbox && !isCloudLinkFresh(cachedState.dropbox)) {
      markCloudUnlinked('dropbox');
    }

    if (this.cloudLinkButtons.drive) {
      this.cloudLinkButtons.drive.textContent = driveLinked ? 'Reauthorize Drive' : 'Link Google Drive';
      this.cloudLinkButtons.drive.disabled = Boolean(this.cloudBusy.drive) || this.recording;
      this.cloudLinkButtons.drive.dataset.state = driveLinked ? 'linked' : 'idle';
    }
    if (this.cloudLinkStatusNodes.drive) {
      this.cloudLinkStatusNodes.drive.textContent = driveLinked ? 'Linked' : 'Not linked';
      this.cloudLinkStatusNodes.drive.dataset.state = driveLinked ? 'linked' : 'idle';
    }
    if (driveLinked && this.pendingDriveUploads.length) {
      this.flushPendingDriveUploads().catch((error) => {
        console.warn('Pending Drive uploads failed to resume', error);
      });
    }

    if (this.cloudLinkButtons.dropbox) {
      this.cloudLinkButtons.dropbox.textContent = dropboxLinked ? 'Refresh Dropbox' : 'Link Dropbox';
      this.cloudLinkButtons.dropbox.disabled = Boolean(this.cloudBusy.dropbox) || this.recording;
      this.cloudLinkButtons.dropbox.dataset.state = dropboxLinked ? 'linked' : 'idle';
    }
    if (this.cloudLinkStatusNodes.dropbox) {
      this.cloudLinkStatusNodes.dropbox.textContent = dropboxLinked ? 'Linked' : 'Not linked';
      this.cloudLinkStatusNodes.dropbox.dataset.state = dropboxLinked ? 'linked' : 'idle';
    }
    if (this.dropboxTokenInput) {
      this.dropboxTokenInput.disabled = Boolean(this.cloudBusy.dropbox) || this.recording;
    }
    this.updateAllDriveActions();
  }

  ensureDropboxTokenFallbackVisible({ focus = false, select = false } = {}) {
    if (this.dropboxTokenRow) {
      this.dropboxTokenRow.hidden = false;
      this.dropboxTokenRow.classList.add('cloud-sync-token--visible');
    }
    if (this.dropboxGuideRow) {
      this.dropboxGuideRow.hidden = false;
      this.dropboxGuideRow.classList.add('cloud-sync-token__guide--visible');
    }
    if (focus && this.dropboxTokenInput) {
      this.dropboxTokenInput.focus();
      if (select && typeof this.dropboxTokenInput.select === 'function') {
        this.dropboxTokenInput.select();
      }
    }
  }

  hideDropboxTokenFallback() {
    if (this.dropboxTokenRow) {
      this.dropboxTokenRow.hidden = true;
      this.dropboxTokenRow.classList.remove('cloud-sync-token--visible');
    }
    if (this.dropboxGuideRow) {
      this.dropboxGuideRow.hidden = true;
      this.dropboxGuideRow.classList.remove('cloud-sync-token__guide--visible');
    }
    if (this.dropboxTokenInput) {
      this.dropboxTokenInput.value = '';
    }
  }

  async handleDiskFolderSelection({ autoEnable = false } = {}) {
    if (!STUDIO_DISK_FEATURE_FLAG || !this.diskFolderButton) {
      return;
    }
    if (typeof window.showDirectoryPicker !== 'function') {
      this.diskStatusNode.textContent = 'Local disk recording requires Chrome, Edge, or Arc.';
      this.diskStatusNode.dataset.state = 'error';
      return;
    }
    try {
      this.diskFolderButton.disabled = true;
      this.diskFolderButton.textContent = 'Waiting for folder…';
      await chooseDiskRecordingDirectory();
      const result = await verifyStoredDiskRecordingDirectory({ requestPermission: true });
      if (!result.ok) {
        throw new Error(result.message || 'Folder verification failed.');
      }
      if (autoEnable || isDiskRecordingEnabled()) {
        setDiskRecordingEnabled(true);
        this.diskRecordingEnabled = true;
      }
      this.diskFolderButton.textContent = 'Change folder';
    } catch (error) {
      console.warn('Disk folder selection failed', error);
      this.diskStatusNode.textContent =
        error?.name === 'AbortError' || /cancel/i.test(error?.message || '')
          ? 'Folder selection cancelled.'
          : error?.message || 'Unable to select folder.';
      this.diskStatusNode.dataset.state = 'error';
    } finally {
      this.diskFolderButton.disabled = false;
      this.updateDiskRecordingUI();
    }
  }

  async handleDiskToggle() {
    if (!STUDIO_DISK_FEATURE_FLAG || !this.diskToggleButton) {
      return;
    }
    if (typeof window.showDirectoryPicker !== 'function') {
      this.diskStatusNode.textContent = 'Browser lacks File System Access API support.';
      this.diskStatusNode.dataset.state = 'error';
      return;
    }
    const meta = readDiskRecordingState();
    if (!meta.folderName) {
      await this.handleDiskFolderSelection({ autoEnable: true });
      return;
    }
    const nextEnabled = !isDiskRecordingEnabled();
    if (nextEnabled) {
      const result = await verifyStoredDiskRecordingDirectory({ requestPermission: true });
      if (!result.ok) {
        this.diskStatusNode.textContent = result.message || 'Unable to access the selected folder.';
        this.diskStatusNode.dataset.state = 'error';
        setDiskRecordingEnabled(false);
        this.diskRecordingEnabled = false;
        this.updateDiskRecordingUI();
        return;
      }
    }
    const finalState = setDiskRecordingEnabled(nextEnabled);
    this.diskRecordingEnabled = Boolean(finalState.enabled);
    this.updateDiskRecordingUI();
  }

  updateDiskRecordingUI() {
    if (!STUDIO_DISK_FEATURE_FLAG || !this.diskControls) {
      return;
    }
    const diskSupported = typeof window.showDirectoryPicker === 'function';
    const meta = readDiskRecordingState();
    const hasFolder = Boolean(meta.folderName);
    const enabled = Boolean(meta.enabled && hasFolder);
    this.diskRecordingEnabled = enabled;
    if (this.diskToggleButton) {
      this.diskToggleButton.disabled = !diskSupported;
      this.diskToggleButton.dataset.state = enabled ? 'enabled' : 'disabled';
      this.diskToggleButton.textContent = enabled ? 'Local disk armed' : 'Setup local disk recording';
      this.diskToggleButton.setAttribute('aria-pressed', enabled ? 'true' : 'false');
    }
    if (this.diskFolderButton) {
      this.diskFolderButton.disabled = !diskSupported;
      this.diskFolderButton.textContent = hasFolder ? 'Change folder' : diskSupported ? 'Choose folder' : 'Not supported';
    }
    if (this.diskStatusNode) {
      if (!diskSupported) {
        this.diskStatusNode.textContent = 'Local disk recording requires the File System Access API.';
        this.diskStatusNode.dataset.state = 'error';
      } else if (!hasFolder) {
        this.diskStatusNode.textContent = 'Folder not selected.';
        this.diskStatusNode.dataset.state = 'pending';
      } else if (meta.lastError) {
        this.diskStatusNode.textContent = `${meta.folderName} — ${meta.lastError}`;
        this.diskStatusNode.dataset.state = 'error';
      } else {
        const verified = meta.lastVerifiedAt ? `Checked ${formatRelativeTime(meta.lastVerifiedAt)}.` : 'Ready to verify.';
        this.diskStatusNode.textContent = `${meta.folderName} · ${verified}`;
        this.diskStatusNode.dataset.state = meta.lastVerifiedAt ? 'ready' : 'pending';
      }
    }
    this.updateReadinessSummary();
  }

  async ensureDiskCaptureReadiness({ interactive = false } = {}) {
    if (!STUDIO_DISK_FEATURE_FLAG || !this.diskRecordingEnabled) {
      return { enabled: false, ready: false };
    }
    const result = await verifyStoredDiskRecordingDirectory({ requestPermission: interactive });
    if (!result.ok) {
      if (this.diskStatusNode) {
        this.diskStatusNode.textContent = result.message || 'Unable to access the selected folder.';
        this.diskStatusNode.dataset.state = 'error';
      }
      setDiskRecordingEnabled(false);
      this.diskRecordingEnabled = false;
      this.updateDiskRecordingUI();
      return {
        enabled: true,
        ready: false,
        error: new Error(result.message || 'Folder unavailable'),
      };
    }
    return {
      enabled: true,
      ready: true,
      folderName: result.folderName,
      verifiedAt: Date.now(),
    };
  }

  async handleDriveLink() {
    if (!this.cloud || this.cloudBusy.drive) {
      return;
    }
    this.cloudBusy.drive = true;
    this.updateCloudLinkUI();
    this.setCloudMessage('drive', 'Requesting Google authorization…', 'info');
    try {
      const client = this.cloud.ensureDriveClient();
      if (!client) {
        throw new Error('Google Drive integration is not available on this build.');
      }
      if (typeof client.ensureInitialized === 'function') {
        await client.ensureInitialized();
      }
      if (typeof client.requestAccessToken === 'function') {
        client.requestAccessToken();
      }
      if (client.promise && typeof client.promise.then === 'function') {
        await client.promise;
      } else {
        await new Promise((resolve) => setTimeout(resolve, 800));
      }
      if (this.cloud.hasDriveAccess()) {
        this.setCloudMessage('drive', 'Google Drive linked. Recordings will upload automatically.', 'success');
        const folder = this.session?.GDRIVE_FOLDERNAME || null;
        markCloudLinked('drive', { folder });
        await this.flushPendingDriveUploads();
      } else {
        this.setCloudMessage('drive', 'Check your popup blocker or try again.', 'warn');
        markCloudUnlinked('drive');
      }
    } catch (error) {
      console.error('Failed to link Google Drive', error);
      this.setCloudMessage('drive', error?.message || 'Failed to link Google Drive.', 'error');
      markCloudUnlinked('drive');
    } finally {
      this.cloudBusy.drive = false;
      this.updateCloudFooter();
    }
  }

  async handleDropboxLink() {
    if (!this.cloud || this.cloudBusy.dropbox) {
      return;
    }
    this.cloudBusy.dropbox = true;
    this.updateCloudLinkUI();
    const providedToken = (this.dropboxTokenInput?.value || '').trim();
    if (providedToken) {
      this.ensureDropboxTokenFallbackVisible();
    }
    const interactive = !providedToken;
    const hasExistingAccess = this.cloud?.hasDropboxAccess();
    const forceReauth = !providedToken && hasExistingAccess;
    const pendingMessage = providedToken
      ? 'Linking Dropbox with the provided token…'
      : hasExistingAccess
        ? 'Refreshing Dropbox session…'
        : 'Waiting for the Dropbox popup to complete…';
    this.setCloudMessage('dropbox', pendingMessage, 'info');
    try {
      if (typeof window.setupDropbox !== 'function') {
        throw new Error('Dropbox uploader is not available in this build.');
      }
      const client = await this.cloud.ensureDropboxClient(providedToken || undefined, { interactive, forceReauth });
      if (client) {
        this.setCloudMessage('dropbox', 'Dropbox linked. Recordings will upload automatically.', 'success');
        if (this.dropboxTokenInput) {
          this.dropboxTokenInput.value = '';
        }
        if (!providedToken) {
          this.hideDropboxTokenFallback();
        }
        markCloudLinked('dropbox');
      } else {
        markCloudUnlinked('dropbox');
        if (providedToken) {
          this.setCloudMessage('dropbox', 'Dropbox rejected the provided token. Double-check and try again.', 'error');
          this.ensureDropboxTokenFallbackVisible({ focus: true, select: true });
        } else {
          this.setCloudMessage('dropbox', 'Dropbox authorization was cancelled. Check your popup blocker and try again.', 'warn');
          this.ensureDropboxTokenFallbackVisible({ focus: true });
        }
      }
    } catch (error) {
      console.error('Failed to init Dropbox', error);
      this.setCloudMessage('dropbox', error?.message || 'Unable to initialise Dropbox.', 'error');
      this.ensureDropboxTokenFallbackVisible({ focus: true });
      markCloudUnlinked('dropbox');
    } finally {
      this.cloudBusy.dropbox = false;
      this.updateCloudFooter();
    }
  }

  createAudioContext() {
    const AudioCtx = window.AudioContext || window.webkitAudioContext;
    if (!AudioCtx) {
      return null;
    }
    return new AudioCtx();
  }

  buildLayout() {
    if (document.getElementById(STUDIO_ROOT_ID)) {
      return;
    }

    const root = createElement('div', '', { id: STUDIO_ROOT_ID });

    // Header
    const header = createElement('header', 'podcast-header');
    const title = createElement('h1', '', { text: 'Podcast Control Room' });
    const statusPill = createElement('div', 'podcast-status-pill');
    statusPill.innerHTML = '<span>Live-ready</span>';
    header.append(title, statusPill);

    // Main layout
    const main = createElement('main', 'podcast-main');

    // Left column (roster + markers)
    const rosterColumn = createElement('div', 'podcast-roster');
    const rosterPanel = createElement('section', 'podcast-panel');
    rosterPanel.append(createElement('h2', '', { text: 'Talent Roster' }));
    this.rosterList = createElement('div', 'roster-list');
    rosterPanel.append(this.rosterList);

    const markersPanel = createElement('section', 'podcast-panel');
    markersPanel.append(createElement('h2', '', { text: 'Session Markers' }));
    this.markerLog = createElement('div', 'marker-log');
    const emptyMarkers = createElement('div', 'empty-state', { text: 'Tap “Add Marker” to drop cue points during recording.' });
    emptyMarkers.dataset.empty = 'true';
    this.markerLog.append(emptyMarkers);
    markersPanel.append(this.markerLog);

    rosterColumn.append(rosterPanel, markersPanel);

    // Right column (controls + timeline)
    const consoleColumn = createElement('div', 'podcast-console');
    const consoleGrid = createElement('div', 'podcast-console-grid');
    consoleColumn.append(consoleGrid);

    const invitePanel = createElement('section', 'podcast-panel invite-panel');
    invitePanel.classList.add('console-grid__span-2');
    invitePanel.append(createElement('h2', '', { text: 'Guest Invites' }));
    const inviteIntro = createElement('p', 'invite-copy', {
      text: 'Share a pro audio-ready link with guests. Tweak processing flags before copying.',
    });
    const inviteLinkRow = createElement('div', 'invite-link-row');
    this.inviteLinkInput = createElement('input', 'invite-link-input', {
      type: 'text',
      readonly: 'true',
      value: '',
    });
    this.inviteLinkInput.addEventListener('focus', () => {
      try {
        this.inviteLinkInput.select();
      } catch (error) {
        console.warn('Invite link select failed', error);
      }
    });
    this.inviteCopyButton = createElement('button', 'invite-link-copy', { type: 'button', text: 'Copy link' });
    this.inviteCopyButton.addEventListener('click', () => this.copyInviteLink());
    inviteLinkRow.append(this.inviteLinkInput, this.inviteCopyButton);
    this.inviteStatusNode = createElement('div', 'invite-status');

    const inviteOptions = createElement('div', 'invite-options');
    const optionDefs = [
      { key: 'proAudio', label: 'Enable pro audio (stereo, 256 kbps)', defaultChecked: true },
      { key: 'disableAec', label: 'Disable echo cancellation', defaultChecked: true },
      { key: 'disableDenoise', label: 'Disable noise reduction', defaultChecked: true },
      { key: 'disableAgc', label: 'Disable auto gain control', defaultChecked: true },
    ];
    optionDefs.forEach((option) => {
      const optionLabel = createElement('label', 'invite-option');
      const checkbox = createElement('input', 'invite-option__checkbox', { type: 'checkbox' });
      checkbox.checked = option.defaultChecked;
      checkbox.addEventListener('change', () => this.updateInviteLink());
      optionLabel.append(checkbox, createElement('span', 'invite-option__label', { text: option.label }));
      inviteOptions.append(optionLabel);
      this.inviteOptionNodes[option.key] = checkbox;
    });

    invitePanel.append(inviteIntro, inviteLinkRow, this.inviteStatusNode, inviteOptions);

    const sessionToolsPanel = createElement('section', 'podcast-panel session-tools');
    sessionToolsPanel.classList.add('console-grid__span-2');
    const sessionToolsGrid = createElement('div', 'session-tools__grid');
    sessionToolsPanel.append(sessionToolsGrid);

    const hostCard = createElement('div', 'session-tool session-tool--host');
    hostCard.append(createElement('h2', 'session-tool__title', { text: 'Host Input' }));
    const hostControls = createElement('div', 'host-input-content');
    this.hostMicButton = createElement('button', 'host-input-toggle', { text: 'Enable Host Mic' });
    this.hostMicButton.addEventListener('click', () => this.handleHostMicToggle());
    this.hostMicStatusNode = createElement('div', 'host-input-status', { text: 'Microphone idle' });
    hostControls.append(this.hostMicButton, this.hostMicStatusNode);
    this.hostMicErrorNode = createElement('div', 'host-input-error');
    hostCard.append(hostControls, this.hostMicErrorNode);
    sessionToolsGrid.append(hostCard);

    const controlCard = createElement('div', 'session-tool session-tool--control');
    controlCard.append(createElement('h2', 'session-tool__title', { text: 'Session Control' }));
    const transportStrip = createElement('div', 'transport-strip');
    this.recordButton = createElement('button', '', { text: 'Arm & Record' });
    this.recordButton.addEventListener('click', () => this.handleRecordToggle());

    this.markerButton = createElement('button', '', { text: 'Add Marker' });
    this.markerButton.disabled = true;
    this.markerButton.addEventListener('click', () => this.addMarker());

    const transportButtons = createElement('div', 'transport-buttons');
    transportButtons.append(this.recordButton, this.markerButton);
    this.sessionInfo = createElement('div', 'session-info', { text: 'Room: ' + (this.roomName || '—') });
    transportStrip.append(transportButtons);
    transportStrip.append(this.sessionInfo);
    this.recordingStatusNode = createElement('div', 'session-recording-status', { text: 'Recording idle' });
    this.recordingStatusNode.dataset.state = 'idle';
    transportStrip.append(this.recordingStatusNode);
    controlCard.append(transportStrip);
    sessionToolsGrid.append(controlCard);

    const cloudCard = createElement('div', 'session-tool session-tool--cloud');
    cloudCard.append(createElement('h2', 'session-tool__title', { text: 'Cloud Sync' }));
    const cloudList = createElement('div', 'cloud-sync-list');
    const driveRow = createElement('div', 'cloud-sync-list__row');
    driveRow.append(createElement('div', 'cloud-sync-list__label', { text: 'Google Drive' }));
    const driveActions = createElement('div', 'cloud-sync-list__actions');
    this.cloudLinkButtons.drive = createElement('button', 'cloud-sync-list__button', { text: 'Link Google Drive' });
    this.cloudLinkButtons.drive.addEventListener('click', () => this.handleDriveLink());
    this.cloudLinkButtons.drive.dataset.state = 'idle';
    this.cloudLinkStatusNodes.drive = createElement('span', 'cloud-sync-list__status', { text: 'Not linked' });
    this.cloudLinkStatusNodes.drive.dataset.state = 'idle';
    driveActions.append(this.cloudLinkButtons.drive, this.cloudLinkStatusNodes.drive);
    driveRow.append(driveActions);
    this.cloudLinkMessages.drive = createElement('div', 'cloud-sync-list__hint');
    this.cloudLinkMessages.drive.dataset.variant = '';
    const driveMessageText = createElement('span', 'cloud-sync-list__hint-text');
    this.cloudLinkMessages.drive.append(driveMessageText);
    this.cloudLinkMessageTextNodes.drive = driveMessageText;
    cloudList.append(driveRow, this.cloudLinkMessages.drive);

    const dropboxRow = createElement('div', 'cloud-sync-list__row');
    dropboxRow.append(createElement('div', 'cloud-sync-list__label', { text: 'Dropbox' }));
    const dropboxActions = createElement('div', 'cloud-sync-list__actions');
    this.cloudLinkButtons.dropbox = createElement('button', 'cloud-sync-list__button', { text: 'Link Dropbox' });
    this.cloudLinkButtons.dropbox.addEventListener('click', () => this.handleDropboxLink());
    this.cloudLinkButtons.dropbox.dataset.state = 'idle';
    this.cloudLinkStatusNodes.dropbox = createElement('span', 'cloud-sync-list__status', { text: 'Not linked' });
    this.cloudLinkStatusNodes.dropbox.dataset.state = 'idle';
    dropboxActions.append(this.cloudLinkButtons.dropbox, this.cloudLinkStatusNodes.dropbox);
    dropboxRow.append(dropboxActions);
    this.cloudLinkMessages.dropbox = createElement('div', 'cloud-sync-list__hint');
    this.cloudLinkMessages.dropbox.dataset.variant = '';
    const dropboxMessageText = createElement('span', 'cloud-sync-list__hint-text');
    this.cloudLinkMessages.dropbox.append(dropboxMessageText);
    this.cloudLinkMessageTextNodes.dropbox = dropboxMessageText;
    const tokenFieldId = 'podcast-dropbox-token';
    const dropboxTokenRow = createElement('div', 'cloud-sync-token');
    this.dropboxTokenRow = dropboxTokenRow;
    const tokenLabel = createElement('label', 'cloud-sync-token__label', { text: 'Access token' });
    tokenLabel.setAttribute('for', tokenFieldId);
    this.dropboxTokenInput = createElement('input', 'cloud-sync-token__input', {
      type: 'password',
      placeholder: 'Paste Dropbox personal access token',
      id: tokenFieldId,
      spellcheck: 'false',
      autocapitalize: 'none',
      autocomplete: 'off',
    });
    dropboxTokenRow.append(tokenLabel, this.dropboxTokenInput);
    const dropboxGuideRow = createElement('div', 'cloud-sync-token__guide');
    this.dropboxGuideRow = dropboxGuideRow;
    const guideLink = createElement('a', 'cloud-sync-guide-link', {
      text: 'Open the Dropbox setup guide',
      href: DROPBOX_GUIDE_URL,
      target: '_blank',
      rel: 'noopener',
    });
    dropboxGuideRow.append('Need a token? ', guideLink);
    this.cloudLinkMessages.dropbox.append(dropboxTokenRow, dropboxGuideRow);
    this.hideDropboxTokenFallback();
    cloudList.append(dropboxRow, this.cloudLinkMessages.dropbox);
    const cloudSummary = createElement('div', 'cloud-sync-summary');
    this.cloudSummaryNode = createElement('div', 'cloud-sync-summary__item', { text: 'Cloud uploads: pending' });
    this.cloudSummaryNode.dataset.state = 'pending';
    const diskSummaryText = STUDIO_DISK_FEATURE_FLAG
      ? 'Disk capture: checking browser support…'
      : 'Disk capture disabled (?studioiso=0)';
    this.diskSummaryNode = createElement('div', 'cloud-sync-summary__item', { text: diskSummaryText });
    this.diskSummaryNode.dataset.state = STUDIO_DISK_FEATURE_FLAG ? 'pending' : 'disabled';
    cloudSummary.append(this.cloudSummaryNode, this.diskSummaryNode);

    const cloudProgress = createElement('div', 'cloud-sync-progress');
    ['drive', 'dropbox'].forEach((service) => {
      const label = this.describeService(service);
      const item = createElement('div', 'cloud-sync-progress__item', { text: `${label} uploads idle` });
      item.dataset.service = service;
      item.dataset.state = 'idle';
      cloudProgress.append(item);
      this.cloudProgressNodes[service] = item;
    });

    cloudCard.append(cloudList, cloudSummary, cloudProgress);
    sessionToolsGrid.append(cloudCard);

    if (STUDIO_DISK_FEATURE_FLAG) {
      const diskCard = createElement('div', 'session-tool session-tool--disk');
      diskCard.append(createElement('h2', 'session-tool__title', { text: 'Local Disk Capture' }));
      const diskSupported = typeof window.showDirectoryPicker === 'function';
      const diskList = createElement('div', 'cloud-sync-list cloud-sync-list--disk');
      this.diskControls = diskList;
      this.diskToggleButton = createElement('button', 'transport-disk__toggle', {
        type: 'button',
        text: 'Setup local disk recording',
      });
      this.diskToggleButton.addEventListener('click', () => this.handleDiskToggle());
      this.diskFolderButton = createElement('button', 'transport-disk__folder', {
        type: 'button',
        text: diskSupported ? 'Choose folder' : 'Not supported',
      });
      this.diskFolderButton.disabled = !diskSupported;
      if (diskSupported) {
        this.diskFolderButton.addEventListener('click', () => this.handleDiskFolderSelection());
      }
      this.diskStatusNode = createElement('div', 'transport-disk__status', {
        text: diskSupported ? 'Folder not selected' : 'File System Access API unavailable',
      });
      diskList.append(this.diskToggleButton, this.diskFolderButton, this.diskStatusNode);
      const diskHintWrapper = createElement('div', 'transport-disk transport-disk--panel');
      const diskHint = createElement('p', 'transport-disk__hint', {
        text: diskSupported
          ? 'Select a folder per session; browsers may prompt again if permissions expire.'
          : 'Use Chromium-based browsers for File System Access support.',
      });
      diskHintWrapper.append(diskHint);
      diskCard.append(diskList, diskHintWrapper);
      sessionToolsGrid.append(diskCard);
      this.updateDiskRecordingUI();
    }

    const timelinePanel = createElement('section', 'podcast-panel timeline-shell');
    timelinePanel.classList.add('console-grid__span-2');
    timelinePanel.append(createElement('h2', '', { text: 'Timeline & Outputs' }));
    this.outputsContainer = createElement('div', 'timeline-surface');
    timelinePanel.append(this.outputsContainer);
    this.showOutputsMessage('Recordings and cue points will appear here.');

    const chatPanel = createElement('section', 'podcast-panel chat-panel');
    chatPanel.dataset.collapsed = 'false';
    this.chatPanel = chatPanel;
    const chatHeaderRow = createElement('div', 'chat-panel__header');
    const chatTitle = createElement('h2', '', { text: 'Control Room Chat' });
    const chatActions = createElement('div', 'chat-panel__actions');
    this.chatPopoutButton = createElement('button', 'chat-panel__action', { type: 'button', text: 'Pop out' });
    this.chatPopoutButton.addEventListener('click', () => this.handleChatPopout());
    this.chatCollapseButton = createElement('button', 'chat-panel__action chat-panel__toggle', {
      type: 'button',
      text: 'Hide chat',
    });
    this.chatCollapseButton.setAttribute('aria-expanded', 'true');
    this.chatCollapseButton.addEventListener('click', () => this.toggleChatPanel());
    chatActions.append(this.chatPopoutButton, this.chatCollapseButton);
    chatHeaderRow.append(chatTitle, chatActions);
    chatPanel.append(chatHeaderRow);

    const chatBody = createElement('div', 'chat-panel__body');
    const legacyChat = document.getElementById('chatModule');
    if (legacyChat) {
      this.chatPlaceholder = document.createElement('div');
      this.chatPlaceholder.dataset.podcastPlaceholder = 'chat-module';
      if (legacyChat.parentNode) {
        legacyChat.parentNode.insertBefore(this.chatPlaceholder, legacyChat);
      }
      legacyChat.classList.remove('hidden');
      legacyChat.dataset.podcastOverlay = 'true';
      const legacyHeader = legacyChat.querySelector('.chat-header');
      if (legacyHeader) {
        const popLink = legacyHeader.querySelector('#popOutChat');
        if (popLink) {
          this.chatPopoutAnchor = popLink;
          popLink.style.display = 'none';
        }
        const closeLink = legacyHeader.querySelector('#closeChat');
        if (closeLink) {
          closeLink.style.display = 'none';
        }
        legacyHeader.dataset.podcastDisplay = legacyHeader.style.display || '';
        legacyHeader.style.display = 'none';
      }
      const legacyResizer = legacyChat.querySelector('.resizer');
      if (legacyResizer) {
        legacyResizer.dataset.podcastDisplay = legacyResizer.style.display || '';
        legacyResizer.style.display = 'none';
      }
      legacyChat.style.position = 'relative';
      legacyChat.style.right = 'auto';
      legacyChat.style.left = 'auto';
      legacyChat.style.bottom = 'auto';
      legacyChat.style.top = 'auto';
      legacyChat.style.zIndex = 'auto';
      legacyChat.style.maxWidth = '100%';
      legacyChat.style.width = '100%';
      legacyChat.style.height = 'auto';
      legacyChat.style.maxHeight = '320px';
      legacyChat.style.overflow = 'hidden';
      legacyChat.style.margin = '0';
      chatBody.append(legacyChat);
      this.chatModule = legacyChat;
    } else {
      chatBody.append(createElement('div', 'chat-panel__empty', { text: 'Chat initialising…' }));
    }
    if (this.chatPopoutButton) {
      const hasPopout = Boolean(this.chatPopoutAnchor || typeof window.createPopoutChat === 'function');
      this.chatPopoutButton.disabled = !hasPopout;
      if (!hasPopout) {
        this.chatPopoutButton.textContent = 'Pop out unavailable';
      }
    }
    chatPanel.append(chatBody);
    this.chatCollapsedHint = createElement('div', 'chat-panel__collapsed-hint', {
      text: 'Chat hidden. Click “Show chat” to reopen.',
    });
    this.chatCollapsedHint.style.display = 'none';
    chatPanel.append(this.chatCollapsedHint);

    if (this.chatModule) {
      this.chatModule.classList.remove('hidden');
      this.chatModule.style.display = '';
      this.chatModule.dataset.podcastEmbedded = 'true';
    }

    consoleGrid.append(invitePanel);
    consoleGrid.append(sessionToolsPanel);
    consoleGrid.append(timelinePanel);
    chatPanel.classList.add('console-grid__span-2');
    consoleGrid.append(chatPanel);

    main.append(rosterColumn, consoleColumn);

    // Footer
    const footer = createElement('footer', 'podcast-footer');
    footer.innerHTML = `
      <div>Powered by VDO.Ninja • Low-latency P2P backbone intact</div>
      <div class="cloud-status">
        <span id="podcast-cloud-drive">${this.cloud?.hasDriveAccess() ? 'Google Drive linked' : 'Drive link pending'}</span>
        <span id="podcast-cloud-dropbox">${this.cloud?.hasDropboxAccess() ? 'Dropbox linked' : 'Dropbox link pending'}</span>
      </div>
    `;

    root.append(header, main, footer);
    document.body.appendChild(root);

    this.driveStatusNode = document.getElementById('podcast-cloud-drive');
    this.dropboxStatusNode = document.getElementById('podcast-cloud-dropbox');
    this.updateHostMicUI();
    this.setHostMicError('');
    this.updateCloudLinkUI();
    this.updateReadinessSummary();
    this.setCloudMessage('drive', '');
    this.setCloudMessage('dropbox', '');
    this.updateInviteLink();
    this.toggleChatPanel(false);
    requestAnimationFrame(() => this.toggleChatPanel(false));
  }

  handleChatPopout() {
    if (this.chatPopoutAnchor && typeof this.chatPopoutAnchor.click === 'function') {
      try {
        this.chatPopoutAnchor.click();
        return;
      } catch (error) {
        console.warn('Legacy chat pop-out click failed', error);
      }
    }
    if (typeof window.createPopoutChat === 'function') {
      try {
        window.createPopoutChat();
        return;
      } catch (error) {
        console.warn('createPopoutChat invocation failed', error);
      }
    }
    try {
      window.open(window.location.href, '_blank', 'noopener');
    } catch (error) {
      console.warn('Fallback chat pop-out failed', error);
    }
  }

  toggleChatPanel(forceCollapsed) {
    const shouldCollapse = typeof forceCollapsed === 'boolean' ? forceCollapsed : !this.chatCollapsed;
    this.chatCollapsed = Boolean(shouldCollapse);
    if (this.chatPanel) {
      this.chatPanel.dataset.collapsed = this.chatCollapsed ? 'true' : 'false';
    }
    if (this.chatCollapseButton) {
      this.chatCollapseButton.textContent = this.chatCollapsed ? 'Show chat' : 'Hide chat';
      this.chatCollapseButton.setAttribute('aria-expanded', this.chatCollapsed ? 'false' : 'true');
    }
    if (this.chatCollapsedHint) {
      this.chatCollapsedHint.style.display = this.chatCollapsed ? '' : 'none';
    }
    if (this.chatModule) {
      if (this.chatCollapsed) {
        this.chatModule.classList.add('hidden');
      } else {
        this.chatModule.classList.remove('hidden');
      }
    }
    if (this.session && typeof this.session.chat !== 'undefined') {
      this.session.chat = !this.chatCollapsed;
    }
  }

  attachRecorderEvents() {
    this.recorder.addEventListener('start', () => {
      if (this.abortUploadsController) {
        this.abortUploadsController.abort();
      }
      this.abortUploadsController = new AbortController();
      this.cleanupDownloadUrls();
      this.trackRuntimeStats.clear();
      this.trackLevelNodes.clear();
      this.teardownSpectrograms();
      this.outputIndicators.clear();
      this.recording = true;
      this.recordStartedAt = Date.now();
      this.recordButton.classList.add('recording');
      this.recordButton.textContent = 'Stop Recording';
      this.markerButton.disabled = false;
      this.showOutputsMessage('Recording… tracks will appear as audio arrives.');
      this.updateHostMicUI();
      this.setUploadProgressPending(true);
      if (this.recordingPlan?.sync) {
        this.recordingPlan.sync.start = {
          wallClock: this.recordStartedAt,
          highRes: snapshotHighResClock(),
        };
      }
      this.logRecordingEvent('record:start', { sessionId: this.recordingSessionId });
      this.updateRecordingPlanStatus('started', { events: this.recordingPlan?.events || [] });
      this.setRecordingStatus('Recording in progress', 'active');
    });

    this.recorder.addEventListener('chunk', (event) => {
      const { participant, trackType, channelIndex } = event.detail || {};
      if (!participant || !trackType) {
        return;
      }
      const channelKey = typeof channelIndex === 'number' ? channelIndex : 0;
      const key = this.buildTrackKey(participant.uuid, trackType, channelKey);
      if (!key) {
        return;
      }
      const indicator = this.ensureOutputIndicator(key, participant, trackType, channelKey);
      if (!indicator) {
        return;
      }
      indicator.badge.textContent = 'Recording';
      indicator.wrapper.dataset.state = 'recording';
      this.updateRecordingRuntimeMetrics(key, indicator, event.detail);
      this.trackManifestChunk(event.detail);
    });

    this.recorder.addEventListener('meter-ready', (event) => {
      const { participant, trackType, channelIndex, meter } = event.detail || {};
      if (!participant?.uuid || trackType !== 'audio') {
        return;
      }
      const key = this.buildTrackKey(participant.uuid, trackType, channelIndex);
      if (!key) {
        return;
      }
      const indicator = this.outputIndicators.get(key);
      if (!indicator) {
        return;
      }
      this.attachSpectrogram(key, indicator, participant, trackType, channelIndex, meter);
    });

    this.recorder.addEventListener('error', (event) => {
      console.error('Recorder error', event.detail);
      this.setStatusMessage('Recorder error: ' + (event.detail?.message || 'unknown'));
    });

    this.recorder.addEventListener('stop', (event) => {
      this.recording = false;
      this.recordButton.classList.remove('recording');
      this.recordButton.textContent = 'Arm & Record';
      this.markerButton.disabled = true;
      this.showOutputsMessage('Finalising recordings…');
      this.trackLevelNodes.clear();
      this.teardownSpectrograms();
      this.presentRecordings(event.detail?.files);
      this.outputIndicators.clear();
      this.trackRuntimeStats.clear();
      this.updateHostMicUI();
      if (this.recordingPlan?.sync) {
        this.recordingPlan.sync.stop = {
          wallClock: Date.now(),
          highRes: snapshotHighResClock(),
        };
      }
      if (this.recordingPlan) {
        this.recordingPlan.files = this.summariseRecordingFiles(event.detail?.files);
        this.logRecordingEvent('record:stop', { fileCount: this.recordingPlan?.files?.length || 0 });
        this.updateRecordingPlanStatus('stopped', {
          files: this.recordingPlan.files,
          events: this.recordingPlan.events,
        });
      }
      this.setRecordingStatus('Recording idle', 'idle');
    });
  }

  ensureOutputIndicator(key, participant, trackType, channelIndex = 0) {
    if (!this.outputsContainer) {
      return null;
    }
    if (this.outputIndicators.has(key)) {
      return this.outputIndicators.get(key);
    }

    this.prepareTracklistSurface();

    if (!this.outputsContainer.dataset.hasTracks) {
      this.outputsContainer.innerHTML = '';
      this.outputsContainer.dataset.hasTracks = 'true';
    }

    const wrapper = createElement('div', 'timeline-track');
    wrapper.dataset.key = key;
    wrapper.dataset.trackType = trackType;
    wrapper.dataset.participant = participant.uuid || '';
    wrapper.dataset.state = 'armed';

    const header = createElement('div', 'timeline-track__header');
    const titleGroup = createElement('div', 'timeline-track__title-group');
    const title = createElement('div', 'timeline-track__title', { text: participant.label || participant.uuid || 'Guest' });
    const descriptorParts = [];
    if (participant.external || participant.uuid === 'host-mic') {
      descriptorParts.push('Local input');
    } else if (participant.streamID) {
      descriptorParts.push(`Stream ${participant.streamID}`);
    }
    descriptorParts.push(trackType ? trackType.toUpperCase() : 'AUDIO');
    descriptorParts.push(`Channel ${channelIndex + 1}`);
    const subtitle = createElement('div', 'timeline-track__subtitle', {
      text: descriptorParts.filter(Boolean).join(' • '),
    });
    titleGroup.append(title, subtitle);
    const badge = createElement('span', 'timeline-track__badge', { text: 'Arming' });
    header.append(titleGroup, badge);

    const metrics = createElement('div', 'timeline-track__metrics');
    const inboundMetric = createElement('span', 'timeline-track__metric timeline-track__metric--inbound', {
      text: participant.external || participant.uuid === 'host-mic' ? 'Inbound: Local capture' : 'Inbound: pending…',
    });
    const recordMetric = createElement('span', 'timeline-track__metric timeline-track__metric--recording', {
      text: 'Recording: waiting…',
    });
    metrics.append(inboundMetric, recordMetric);

    const waveform = createElement('div', 'timeline-track__waveform');
    const spectrogramCanvas = document.createElement('canvas');
    spectrogramCanvas.className = 'timeline-track__spectrogram';
    const waveFill = createElement('div', 'timeline-track__wavefill');
    waveform.append(spectrogramCanvas, waveFill);

    wrapper.append(header, metrics, waveform);
    this.outputsContainer.append(wrapper);

    const indicator = {
      key,
      wrapper,
      badge,
      inboundMetric,
      recordMetric,
      waveFill,
      spectrogramCanvas,
      participant,
      trackType,
      channelIndex,
    };

    this.outputIndicators.set(key, indicator);
    this.registerTrackLevelNode(participant.uuid, waveFill);
    this.updateTrackInboundMetric(participant.uuid);
    this.attachSpectrogram(key, indicator, participant, trackType, channelIndex);
    return indicator;
  }

  setStatusMessage(message) {
    this.showOutputsMessage(message);
  }

  showOutputsMessage(text) {
    if (!this.outputsContainer) {
      return;
    }
    this.outputsContainer.dataset.mode = 'message';
    this.outputsContainer.dataset.hasTracks = '';
    this.outputsContainer.classList.remove('timeline-tracklist');
    this.outputsContainer.classList.remove('timeline-results');
    this.outputsContainer.innerHTML = '';
    if (typeof text === 'string' && text.trim()) {
      this.outputsContainer.append(createElement('div', 'timeline-placeholder', { text }));
    } else {
      this.outputsContainer.append(createElement('div', 'timeline-placeholder', { text: '' }));
    }
  }

  prepareTracklistSurface({ reset = false } = {}) {
    if (!this.outputsContainer) {
      return;
    }
    const switchingMode = this.outputsContainer.dataset.mode !== 'recording';
    if (switchingMode || reset) {
      this.outputsContainer.innerHTML = '';
      this.outputsContainer.dataset.hasTracks = '';
    }
    this.outputsContainer.dataset.mode = 'recording';
    this.outputsContainer.classList.add('timeline-tracklist');
    this.outputsContainer.classList.remove('timeline-results');
  }

  buildTrackKey(uuid, trackType, channelIndex = 0) {
    if (!uuid || !trackType) {
      return '';
    }
    const index = typeof channelIndex === 'number' ? channelIndex : 0;
    return `${uuid}-${trackType}-${index}`;
  }

  getMeterForTrack(uuid, trackType, channelIndex = 0) {
    if (!this.recorder || typeof this.recorder.getTrackMeter !== 'function') {
      return null;
    }
    return this.recorder.getTrackMeter(uuid, trackType, channelIndex);
  }

  attachSpectrogram(key, indicator, participant, trackType, channelIndex, meterOverride = null) {
    if (!key || trackType !== 'audio' || !indicator?.spectrogramCanvas) {
      return;
    }
    let renderer = this.spectrograms.get(key);
    if (!renderer) {
      renderer = new SpectrogramRenderer(indicator.spectrogramCanvas);
      this.spectrograms.set(key, renderer);
    }
    if (!participant?.uuid) {
      return;
    }
    const meter = meterOverride || this.getMeterForTrack(participant.uuid, trackType, channelIndex);
    if (meter?.analyser) {
      renderer.setAnalyser(meter.analyser);
    }
  }

  teardownSpectrograms() {
    if (!this.spectrograms) {
      return;
    }
    this.spectrograms.forEach((renderer) => {
      if (renderer && typeof renderer.destroy === 'function') {
        renderer.destroy();
      }
    });
    this.spectrograms.clear();
  }

  registerTrackLevelNode(uuid, node) {
    if (!uuid || !node) {
      return;
    }
    if (!this.trackLevelNodes.has(uuid)) {
      this.trackLevelNodes.set(uuid, new Set());
    }
    this.trackLevelNodes.get(uuid).add(node);
  }

  updateTrackLevelVisual(uuid, level) {
    if (!uuid) {
      return;
    }
    const nodes = this.trackLevelNodes.get(uuid);
    if (!nodes || !nodes.size) {
      return;
    }
    const normalized = Math.max(0.08, Math.min(1, (level || 0) / 100));
    nodes.forEach((node) => {
      if (!node) {
        return;
      }
      node.style.transform = `scaleY(${normalized})`;
      node.style.opacity = level > 3 ? '0.95' : '0.45';
    });
  }

  captureParticipantMetrics(participant) {
    if (!participant?.uuid) {
      return;
    }
    const next = { ...(this.participantMetrics.get(participant.uuid) || {}) };
    if (typeof participant.audioBitrateKbps === 'number' && participant.audioBitrateKbps >= 0) {
      next.audioBitrateKbps = participant.audioBitrateKbps;
    }
    if (participant.audioCodec) {
      next.audioCodec = participant.audioCodec;
    }
    if (participant.external || participant.uuid === 'host-mic') {
      next.local = true;
    }
    this.participantMetrics.set(participant.uuid, next);
    this.updateTrackInboundMetric(participant.uuid, next);
  }

  updateTrackInboundMetric(uuid, metrics = this.participantMetrics.get(uuid)) {
    if (!uuid) {
      return;
    }
    const resolvedMetrics = metrics || null;
    const indicators = this.outputIndicators || new Map();
    indicators.forEach((indicator) => {
      if (!indicator || !indicator.participant || indicator.participant.uuid !== uuid) {
        return;
      }
      const node = indicator.inboundMetric;
      if (!node) {
        return;
      }
      if (resolvedMetrics?.local) {
        node.textContent = 'Inbound: Local capture';
        return;
      }
      const parts = [];
      if (resolvedMetrics && typeof resolvedMetrics.audioBitrateKbps === 'number' && resolvedMetrics.audioBitrateKbps > 0) {
        const formatted = this.formatBitrate(resolvedMetrics.audioBitrateKbps);
        if (formatted) {
          parts.push(formatted);
        }
      }
      if (resolvedMetrics?.audioCodec) {
        parts.push(resolvedMetrics.audioCodec.toUpperCase());
      }
      node.textContent = parts.length ? `Inbound: ${parts.join(' • ')}` : 'Inbound: pending…';
    });
  }

  updateRecordingRuntimeMetrics(key, indicator, detail) {
    if (!indicator) {
      return;
    }
    const runtime = this.trackRuntimeStats.get(key) || {
      bytes: 0,
      startedAt: Date.now(),
      lastUpdate: Date.now(),
    };
    const chunk = detail?.data;
    if (chunk && typeof chunk.size === 'number') {
      runtime.bytes += chunk.size;
    }
    const now = Date.now();
    if (!runtime.startedAt) {
      runtime.startedAt = now;
    }
    runtime.lastUpdate = now;
    const elapsedMs = Math.max(1, now - runtime.startedAt);
    const kbps = runtime.bytes ? (runtime.bytes * 8) / elapsedMs : 0;
    const durationSeconds = (now - runtime.startedAt) / 1000;
    const sampleRate = this.recorder?.options?.targetSampleRate || 48000;
    const sampleRateLabel =
      sampleRate >= 1000
        ? `${(sampleRate / 1000).toFixed(sampleRate % 1000 === 0 ? 0 : 1)} kHz`
        : `${sampleRate} Hz`;
    const bitrateLabel = kbps > 0 ? `${Math.round(kbps)} kbps` : 'estimating…';
    const durationLabel = this.formatDuration(durationSeconds);
    indicator.recordMetric.textContent = `Recording: ${bitrateLabel} • WAV ${sampleRateLabel} • ${durationLabel}`;
    this.trackRuntimeStats.set(key, runtime);
  }

  buildRecordingPlanContext({ diskInfo } = {}) {
    const now = Date.now();
    const cloudSnapshot = readCloudLinkStatus();
    const plan = {
      sessionId: createRecordingSessionId(),
      conductor: 'studio',
      preparedAt: now,
      disk: {
        enabled: Boolean(diskInfo?.ready),
        folderName: diskInfo?.folderName || null,
        verifiedAt: diskInfo?.verifiedAt || null,
      },
      cloud: {
        driveLinked: Boolean(this.cloud?.hasDriveAccess() || cloudSnapshot.drive),
        dropboxLinked: Boolean(this.cloud?.hasDropboxAccess() || cloudSnapshot.dropbox),
        snapshot: cloudSnapshot,
      },
      sync: {
        prepared: snapshotHighResClock(),
        start: null,
        stop: null,
      },
      participants: {},
      files: [],
      events: [],
    };
    this.recordingPlan = plan;
    this.recordingSessionId = plan.sessionId;
    this.logRecordingEvent('record:plan', { sessionId: plan.sessionId });
    dispatchStudioEvent(PODCAST_RECORD_PLAN_EVENT, { plan });
    this.setRecordingStatus('Recording plan armed', 'armed');
    return plan;
  }

  updateRecordingPlanStatus(status, extra = {}) {
    if (!this.recordingPlan) {
      return;
    }
    const detail = {
      status,
      plan: this.recordingPlan,
      timestamp: Date.now(),
      ...extra,
    };
    dispatchStudioEvent(PODCAST_RECORD_STATUS_EVENT, detail);
  }

  trackManifestChunk(detail) {
    if (!this.recordingPlan || !detail?.participant?.uuid) {
      return;
    }
    const participantId = detail.participant.uuid;
    if (!this.recordingPlan.participants[participantId]) {
      this.recordingPlan.participants[participantId] = {
        participantId,
        label: detail.participant.label || participantId,
        tracks: {},
      };
    }
    const participantPlan = this.recordingPlan.participants[participantId];
    const trackKey = `${detail.trackType || 'audio'}:${typeof detail.channelIndex === 'number' ? detail.channelIndex : 0}`;
    if (!participantPlan.tracks[trackKey]) {
      participantPlan.tracks[trackKey] = {
        trackType: detail.trackType || 'audio',
        channelIndex: typeof detail.channelIndex === 'number' ? detail.channelIndex : 0,
        segments: [],
        totalBytes: 0,
        sequence: 0,
      };
    }
    const track = participantPlan.tracks[trackKey];
    const bytes = detail.data?.size || 0;
    track.sequence += 1;
    track.totalBytes += bytes;
    const timecodeMs = this.recordStartedAt ? Date.now() - this.recordStartedAt : 0;
    const segment = {
      sequence: track.sequence,
      bytes,
      receivedAt: Date.now(),
      timecodeMs,
    };
    if (track.segments.length > 48) {
      track.segments.shift();
    }
    track.segments.push(segment);
  }

  summariseRecordingFiles(filesMap) {
    if (!filesMap || typeof filesMap.forEach !== 'function') {
      return [];
    }
    const summaries = [];
    filesMap.forEach((meta) => {
      if (!meta) {
        return;
      }
      summaries.push({
        participant: meta.participant?.uuid || null,
        label: meta.participant?.label || null,
        trackType: meta.trackType,
        channelIndex: meta.channelIndex,
        filename: meta.filename,
        mimeType: meta.mimeType,
        size: meta.size,
        durationSeconds: meta.durationSeconds,
      });
    });
    return summaries;
  }

  logRecordingEvent(type, data = {}) {
    if (!type) {
      return;
    }
    if (!this.recordingPlan) {
      this.recordingPlan = {
        sessionId: createRecordingSessionId(),
        events: [],
      };
    }
    if (!Array.isArray(this.recordingPlan.events)) {
      this.recordingPlan.events = [];
    }
    const timestamp = Date.now();
    const timecodeMs = this.recordStartedAt ? Math.max(0, timestamp - this.recordStartedAt) : 0;
    this.recordingPlan.events.push({
      type,
      timestamp,
      timecodeMs,
      data,
    });
    if (this.recordingPlan.events.length > 2000) {
      this.recordingPlan.events.shift();
    }
  }

  setRecordingStatus(text, state = 'idle') {
    if (!this.recordingStatusNode) {
      return;
    }
    this.recordingStatusNode.textContent = text;
    this.recordingStatusNode.dataset.state = state;
  }

  formatBitrate(value) {
    if (!Number.isFinite(value) || value <= 0) {
      return null;
    }
    if (value >= 1000) {
      const megabits = value / 1000;
      return `${megabits.toFixed(megabits >= 10 ? 0 : 1)} Mbps`;
    }
    return `${Math.round(value)} kbps`;
  }

  formatDuration(seconds) {
    if (!Number.isFinite(seconds) || seconds < 0) {
      return '0:00';
    }
    const totalSeconds = Math.floor(seconds);
    const hours = Math.floor(totalSeconds / 3600);
    const minutes = Math.floor((totalSeconds % 3600) / 60);
    const secs = totalSeconds % 60;
    if (hours > 0) {
      return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
    }
    return `${minutes}:${secs.toString().padStart(2, '0')}`;
  }

  async handleRecordToggle() {
    if (this.recording) {
      this.showOutputsMessage('Wrapping up recording…');
      this.logRecordingEvent('record:stop:requested', { reason: 'host-toggle' });
      this.setRecordingStatus('Stopping recording…', 'stopping');
      try {
        await this.recorder.stop();
      } catch (error) {
        console.error('Failed to stop recorder cleanly', error);
        this.setStatusMessage('Recording stop failed: ' + (error?.message || 'unknown error'));
      }
      return;
    }
    try {
      let diskInfo = null;
      if (STUDIO_DISK_FEATURE_FLAG && this.diskRecordingEnabled) {
        diskInfo = await this.ensureDiskCaptureReadiness({ interactive: true });
        if (diskInfo && diskInfo.error) {
          this.setStatusMessage(diskInfo.error.message || 'Disk folder not accessible.');
          return;
        }
      }
      this.buildRecordingPlanContext({ diskInfo });
      this.logRecordingEvent('record:arm', { source: 'host-toggle' });
      this.updateRecordingPlanStatus('armed', { events: this.recordingPlan?.events || [] });
      this.setRecordingStatus('Arming recorders…', 'arming');
      await this.recorder.start({
        includeVideo: false,
        includeLocal: false,
        extraParticipants: this.getAdditionalRecordingParticipants(),
      });
    } catch (error) {
      console.error('Failed to start recorder', error);
      this.setStatusMessage('Unable to start recording: ' + (error?.message || 'unknown error'));
      this.updateHostMicUI();
      this.logRecordingEvent('record:error', { stage: 'start', message: error?.message || 'unknown error' });
      this.setRecordingStatus('Recording idle', 'error');
      this.updateRecordingPlanStatus('error', { error: error?.message || 'start failed', events: this.recordingPlan?.events || [] });
      this.setUploadProgressPending(false);
    }
  }

  async presentRecordings(filesMap) {
    const files = filesMap || this.recorder.getFiles();
    if (!files || files.size === 0) {
      this.showOutputsMessage('No media captured.');
      this.setUploadProgressPending(false);
      return;
    }
    this.cleanupDownloadUrls();
    this.outputsContainer.dataset.mode = 'results';
    this.outputsContainer.dataset.hasTracks = '';
    this.outputsContainer.classList.remove('timeline-tracklist');
    this.outputsContainer.classList.add('timeline-results');
    this.outputsContainer.innerHTML = '';
    const uploadPromises = [];
    this.setUploadProgressPending(false);
    files.forEach((meta, key) => {
      if (!meta?.blob) {
        return;
      }
      const wrapper = createElement('div', 'timeline-entry');
      wrapper.dataset.key = key;
      const label = `${meta.trackType.toUpperCase()} • ${meta.participant.label || meta.participant.uuid}`;
      const downloadUrl = URL.createObjectURL(meta.blob);
      this.activeDownloadUrls.push(downloadUrl);
      const linkLabel = meta.mimeType === 'audio/wav' ? 'Download WAV' : 'Download';
      const link = createElement('a', 'marker-badge', { text: linkLabel, href: downloadUrl });
      const fallbackExtension = meta.mimeType?.split('/')?.[1] || 'webm';
      link.download = meta.filename || `${meta.participant.streamID || meta.participant.uuid}-${meta.trackType}.${fallbackExtension}`;
      const header = createElement('div', 'timeline-entry-header');
      header.append(createElement('span', 'timeline-entry-label', { text: label }), link);
      wrapper.append(header);
      const metaSummary = this.describeTrackMeta(meta) || 'Metadata pending';
      const metaLine = createElement('div', 'upload-meta', { text: metaSummary });
      if (meta.packagingError) {
        metaLine.textContent += metaSummary ? ' • fallback export' : 'Fallback export';
      }
      wrapper.append(metaLine);
      const statusContainer = createElement('div', 'upload-status');
      const driveLine = this.createServiceStatusLine('drive');
      const dropboxLine = this.createServiceStatusLine('dropbox');
      statusContainer.append(driveLine, dropboxLine);
      wrapper.append(statusContainer);
      this.outputsContainer.append(wrapper);
      uploadPromises.push(
        this.queueCloudUpload(meta, {
          drive: driveLine,
          dropbox: dropboxLine,
        }),
      );
    });
    if (uploadPromises.length) {
      try {
        await Promise.allSettled(uploadPromises);
      } catch (error) {
        console.warn('One or more uploads failed', error);
      }
    }
    this.updateCloudFooter();
  }

  addMarker() {
    const timestamp = this.recordStartedAt ? (Date.now() - this.recordStartedAt) / 1000 : 0;
    const note = {
      time: timestamp,
      label: `Marker @ ${timestamp.toFixed(1)}s`,
    };
    this.markers.push(note);
    this.logRecordingEvent('marker', { label: note.label, timeSeconds: note.time });
    this.renderMarkers();
  }

  renderMarkers() {
    this.markerLog.innerHTML = '';
    if (!this.markers.length) {
      const empty = createElement('div', 'empty-state', { text: 'Tap “Add Marker” to drop cue points during recording.' });
      empty.dataset.empty = 'true';
      this.markerLog.append(empty);
      return;
    }
    this.markers.forEach((marker, index) => {
      const item = createElement('div', 'marker-item');
      item.append(createElement('span', '', { text: marker.label }));
      item.append(createElement('span', 'marker-badge', { text: `#${index + 1}` }));
      this.markerLog.append(item);
    });
  }

  startRosterLoop() {
    if (this.rosterTimer) {
      clearInterval(this.rosterTimer);
    }
    this.rosterTimer = setInterval(() => this.refreshRoster(), ROSTER_REFRESH_MS);
  }

  refreshRoster() {
    if (!this.session) {
      return;
    }
    this.updateRoomIndicator();
    const baseParticipants = collectParticipants(this.session);
    const participants = [...baseParticipants];
    this.virtualParticipants.forEach((participant) => {
      if (participant) {
        participants.push(participant);
      }
    });
    const activeIds = new Set();

    participants.forEach((participant) => {
      activeIds.add(participant.uuid);
      this.captureParticipantMetrics(participant);
      const existing = this.rosterItems.get(participant.uuid);
      if (existing) {
        this.updateRosterItem(existing, participant);
      } else {
        const item = this.createRosterItem(participant);
        this.rosterItems.set(participant.uuid, item);
        this.rosterList.append(item);
      }
    });

    Array.from(this.rosterItems.keys()).forEach((uuid) => {
      if (!activeIds.has(uuid)) {
        const node = this.rosterItems.get(uuid);
      if (node?.parentNode) {
        node.parentNode.removeChild(node);
      }
      this.rosterItems.delete(uuid);
      this.meterValues.delete(uuid);
      this.teardownDriveControl(uuid);
      if (this.remoteOverlay && this.remoteOverlay.dataset.activeUuid === uuid) {
        this.closeRemoteOverlay();
      }
    }
  });
  }

  createRosterItem(participant) {
    const item = createElement('div', 'roster-item');
    item.dataset.uuid = participant.uuid;
    item.dataset.status = participant.status || 'connecting';
    if (participant.role) {
      item.dataset.role = participant.role;
    }

    const meta = createElement('div', 'roster-meta');
    meta.append(createElement('div', 'roster-name', { text: participant.label }));
    const idText = participant.streamID ? `Stream: ${participant.streamID}` : 'Awaiting stream';
    meta.append(createElement('div', 'roster-id', { text: idText }));
    const descriptorText = this.describeParticipantRole(participant);
    if (descriptorText) {
      meta.append(createElement('div', 'roster-role', { text: descriptorText }));
    }

    const meter = createElement('div', 'meter-bar', { 'data-meter': participant.uuid });
   meter.append(createElement('div', 'meter-bar-fill'));

    item.append(meta, meter);

    const actions = createElement('div', 'roster-actions');
    const actionRow = createElement('div', 'roster-action-row');
    let hasActions = false;
    if (participant.role !== 'host-mic') {
      const controlButton = createElement('button', 'roster-action-button', {
        type: 'button',
        text: 'Remote Controls',
      });
      controlButton.addEventListener('click', () => this.openRemoteControls(participant.uuid));
      actionRow.append(controlButton);
      hasActions = true;
    }
    const driveControls = this.createDriveControl(participant);
    if (driveControls) {
      actionRow.append(driveControls.button);
      hasActions = true;
    }
    if (hasActions) {
      actions.append(actionRow);
      if (driveControls?.status) {
        actions.append(driveControls.status);
      }
      item.append(actions);
    }

    this.updateRosterItem(item, participant);
    return item;
  }

  updateRosterItem(item, participant) {
    item.dataset.status = participant.status || 'connecting';
    const name = item.querySelector('.roster-name');
    if (name) {
      name.textContent = participant.label;
    }
    const id = item.querySelector('.roster-id');
    if (id) {
      id.textContent = participant.streamID ? `Stream: ${participant.streamID}` : 'Awaiting stream';
    }
    item.dataset.role = participant.role || '';
    const descriptor = item.querySelector('.roster-role');
    if (descriptor) {
      const descriptorText = this.describeParticipantRole(participant);
      descriptor.textContent = descriptorText || '';
      descriptor.style.display = descriptorText ? '' : 'none';
    }
    this.applyMeterValue(participant.uuid, participant.audioLevel || 0);
    this.updateDriveActionAvailability(participant.uuid);
  }

  createDriveControl(participant) {
    if (!participant || participant.role === 'host-mic' || !participant.uuid) {
      return null;
    }
    if (typeof window === 'undefined' || typeof window.requestGoogleDriveRecord !== 'function') {
      return null;
    }
    const button = createElement('button', 'roster-action-button roster-action-button--drive', {
      type: 'button',
      text: 'Drive Upload',
    });
    button.dataset.uuid = participant.uuid;
    button.addEventListener('click', () => this.handleDriveRecordToggle(participant.uuid));

    const status = createElement('div', 'roster-drive-status', { text: DRIVE_STATUS_MESSAGES.idle });
    status.dataset.state = 'idle';
    status.dataset.uuid = participant.uuid;

    this.rosterDriveButtons.set(participant.uuid, button);
    this.rosterDriveStatuses.set(participant.uuid, status);
    this.updateDriveActionAvailability(participant.uuid);
    this.applyDriveSnapshot(participant.uuid);

    return { button, status };
  }

  teardownDriveControl(uuid) {
    if (!uuid) {
      return;
    }
    if (this.driveStatusResetTimers.has(uuid)) {
      clearTimeout(this.driveStatusResetTimers.get(uuid));
      this.driveStatusResetTimers.delete(uuid);
    }
    this.rosterDriveButtons.delete(uuid);
    this.rosterDriveStatuses.delete(uuid);
    this.driveProgressSnapshots.delete(uuid);
  }

  findLegacyDriveButton(uuid) {
    if (!uuid || typeof document === 'undefined') {
      return null;
    }
    return document.querySelector('[data-action-type="recorder-google-drive-remote"][data--u-u-i-d="' + uuid + '"]');
  }

  canTriggerDriveUpload() {
    if (typeof window === 'undefined' || typeof window.requestGoogleDriveRecord !== 'function') {
      return false;
    }
    return Boolean(this.cloud?.hasDriveAccess());
  }

  async handleDriveRecordToggle(uuid) {
    if (!uuid) {
      return;
    }
    const button = this.rosterDriveButtons.get(uuid);
    if (!button) {
      return;
    }
    if (typeof window === 'undefined' || typeof window.requestGoogleDriveRecord !== 'function') {
      this.setRosterDriveStatus(uuid, 'error', 'Drive controls unavailable in this build.');
      return;
    }
    const legacyButton = this.findLegacyDriveButton(uuid);
    if (!legacyButton) {
      this.setRosterDriveStatus(uuid, 'pending', 'Guest controls preparing…');
      this.updateDriveActionAvailability(uuid);
      return;
    }
    const isActive = legacyButton.classList?.contains('pressed');
    if (!isActive && !this.canTriggerDriveUpload()) {
      this.setRosterDriveStatus(uuid, 'error', 'Link Google Drive above to enable uploads.');
      this.updateDriveActionAvailability(uuid);
      return;
    }
    button.dataset.pending = 'true';
    button.disabled = true;
    try {
      if (isActive) {
        await window.requestGoogleDriveRecord(legacyButton, false);
        this.setRosterDriveStatus(uuid, 'idle', DRIVE_STATUS_MESSAGES.idle);
      } else {
        this.setRosterDriveStatus(uuid, 'pending', 'Requesting Drive upload…');
        await window.requestGoogleDriveRecord(legacyButton);
      }
    } catch (error) {
      const message = error?.message || 'Drive request cancelled';
      this.setRosterDriveStatus(uuid, 'error', message);
    } finally {
      button.dataset.pending = 'false';
      this.updateDriveActionAvailability(uuid);
    }
  }

  updateDriveActionAvailability(uuid) {
    const button = this.rosterDriveButtons.get(uuid);
    if (!button) {
      return;
    }
    const hasRequestApi = typeof window !== 'undefined' && typeof window.requestGoogleDriveRecord === 'function';
    const legacyButton = this.findLegacyDriveButton(uuid);
    const hasLegacyControl = Boolean(legacyButton);
    const isActive = Boolean(legacyButton?.classList?.contains('pressed'));
    const pending = button.dataset.pending === 'true';

    let disabled = pending || !hasRequestApi;
    let title = '';

    if (!hasRequestApi) {
      title = 'Drive controls are not available in this build.';
    } else if (!hasLegacyControl) {
      title = 'Guest controls are still initialising.';
      disabled = true;
    } else if (isActive) {
      title = 'Stop this guest’s Drive upload.';
      disabled = pending;
    } else if (!this.canTriggerDriveUpload()) {
      title = 'Link Google Drive above to enable uploads.';
      disabled = true;
    } else {
      title = 'Ask this guest to upload to Drive.';
      disabled = pending;
    }

    button.disabled = disabled;
    button.textContent = isActive ? 'Stop Drive Upload' : 'Drive Upload';
    button.dataset.state = isActive ? 'active' : 'idle';
    if (title) {
      button.title = title;
    }
  }

  updateAllDriveActions() {
    this.rosterDriveButtons.forEach((_, uuid) => this.updateDriveActionAvailability(uuid));
  }

  setRosterDriveStatus(uuid, state = 'idle', text) {
    const node = this.rosterDriveStatuses.get(uuid);
    if (!node) {
      return;
    }
    if (this.driveStatusResetTimers.has(uuid)) {
      clearTimeout(this.driveStatusResetTimers.get(uuid));
      this.driveStatusResetTimers.delete(uuid);
    }
    const label = text || DRIVE_STATUS_MESSAGES[state] || DRIVE_STATUS_MESSAGES.idle;
    node.dataset.state = state;
    node.textContent = label;
    if (state === 'done') {
      const timer = setTimeout(() => {
        this.setRosterDriveStatus(uuid, 'idle', DRIVE_STATUS_MESSAGES.idle);
        this.driveStatusResetTimers.delete(uuid);
      }, DRIVE_STATUS_RESET_MS);
      this.driveStatusResetTimers.set(uuid, timer);
    }
  }

  applyDriveSnapshot(uuid) {
    const snapshot = this.driveProgressSnapshots.get(uuid);
    if (!snapshot) {
      return;
    }
    this.setRosterDriveStatusFromSnapshot(uuid, snapshot);
  }

  setRosterDriveStatusFromSnapshot(uuid, gdrive) {
    if (!gdrive) {
      this.setRosterDriveStatus(uuid, 'idle', DRIVE_STATUS_MESSAGES.idle);
      return;
    }
    if (gdrive.state === 2) {
      this.setRosterDriveStatus(uuid, 'done', DRIVE_STATUS_MESSAGES.done);
      return;
    }
    if (typeof gdrive.rec === 'number' && gdrive.rec > 0) {
      const percent = Math.min(100, Math.round((gdrive.up / Math.max(1, gdrive.rec)) * 100));
      this.setRosterDriveStatus(uuid, 'uploading', `Drive upload ${percent}%`);
    } else {
      this.setRosterDriveStatus(uuid, 'pending', DRIVE_STATUS_MESSAGES.pending);
    }
  }

  handleDriveProgressEvent(event) {
    const detail = event?.detail;
    if (!detail || !detail.UUID) {
      return;
    }
    const { UUID: uuid, gdrive } = detail;
    this.driveProgressSnapshots.set(uuid, gdrive || null);
    if (!this.rosterDriveStatuses.has(uuid)) {
      return;
    }
    this.setRosterDriveStatusFromSnapshot(uuid, gdrive || null);
    this.updateDriveActionAvailability(uuid);
  }

  ensureRemoteOverlay() {
    if (this.remoteOverlay && this.remoteOverlayContent) {
      return this.remoteOverlay;
    }
    const overlay = createElement('div', 'remote-overlay');
    overlay.dataset.podcastOverlay = 'true';
    overlay.dataset.visible = 'false';

    const panel = createElement('div', 'remote-overlay__panel');
    const header = createElement('div', 'remote-overlay__header');
    const title = createElement('h3', 'remote-overlay__title', { text: 'Remote controls' });
    const closeButton = createElement('button', 'remote-overlay__close', { type: 'button', text: 'Close' });
    closeButton.addEventListener('click', () => this.closeRemoteOverlay());
    header.append(title, closeButton);

    const body = createElement('div', 'remote-overlay__body');
    panel.append(header, body);
    overlay.append(panel);

    overlay.addEventListener('click', (event) => {
      if (event.target === overlay) {
        this.closeRemoteOverlay();
      }
    });

    document.body.appendChild(overlay);
    this.remoteOverlay = overlay;
    this.remoteOverlayContent = body;
    return overlay;
  }

  restoreRemoteControls() {
    const state = this.remoteControlState;
    if (!state || !state.element) {
      if (this.remoteOverlay) {
        delete this.remoteOverlay.dataset.activeUuid;
      }
      return;
    }
    const { element, placeholder, wrapper } = state;
    try {
      if (wrapper && wrapper.parentNode) {
        wrapper.parentNode.removeChild(wrapper);
      }
    } catch (error) {
      console.warn('Failed to remove remote controls wrapper', error);
    }
    if (placeholder && placeholder.parentNode) {
      try {
        placeholder.parentNode.insertBefore(element, placeholder);
        placeholder.parentNode.removeChild(placeholder);
      } catch (error) {
        console.warn('Failed to restore remote controls container', error);
      }
    }
    this.remoteControlState = {
      activeUuid: null,
      element: null,
      placeholder: null,
      wrapper: null,
    };
    if (this.remoteOverlay) {
      delete this.remoteOverlay.dataset.activeUuid;
    }
  }

  openRemoteControls(uuid) {
    if (!uuid) {
      return;
    }
    if (this.remoteControlState?.activeUuid && this.remoteControlState.activeUuid !== uuid) {
      this.restoreRemoteControls();
    }
    const overlay = this.ensureRemoteOverlay();
    const body = this.remoteOverlayContent;
    if (!overlay || !body) {
      return;
    }
    body.innerHTML = '';

    const rosterNode = this.rosterItems.get(uuid);
    let label = '';
    if (rosterNode) {
      const nameNode = rosterNode.querySelector('.roster-name');
      label = nameNode ? nameNode.textContent : '';
    }
    const headerTitle = overlay.querySelector('.remote-overlay__title');
    if (headerTitle) {
      headerTitle.textContent = label ? `Remote controls • ${label}` : 'Remote controls';
    }

    const existingState = this.remoteControlState || {};
    if (existingState.activeUuid && existingState.activeUuid === uuid && existingState.wrapper) {
      body.append(existingState.wrapper);
      overlay.dataset.visible = 'true';
      overlay.dataset.activeUuid = uuid;
      return;
    }

    const source = document.getElementById(`container_${uuid}`);
    if (!source) {
      body.append(
        createElement('div', 'remote-overlay__empty', {
          text: 'Legacy director controls are still loading. Try again once the guest is fully connected.',
        }),
      );
      overlay.dataset.visible = 'true';
      overlay.dataset.activeUuid = uuid;
      return;
    }

    const placeholder = document.createElement('div');
    placeholder.dataset.podcastPlaceholder = 'remote-controls';
    source.parentNode?.insertBefore(placeholder, source);

    source.classList.remove('hidden');

    const wrapper = createElement('div', 'remote-overlay__legacy');
    wrapper.dataset.uuid = uuid;
    wrapper.append(source);
    body.append(wrapper);

    this.remoteControlState = {
      activeUuid: uuid,
      element: source,
      placeholder,
      wrapper,
    };

    overlay.dataset.visible = 'true';
    overlay.dataset.activeUuid = uuid;
  }

  closeRemoteOverlay() {
    if (!this.remoteOverlay) {
      return;
    }
    this.restoreRemoteControls();
    this.remoteOverlay.dataset.visible = 'false';
    if (this.remoteOverlayContent) {
      this.remoteOverlayContent.innerHTML = '';
    }
  }

  describeParticipantRole(participant) {
    if (!participant) {
      return '';
    }
    if (participant.role === 'host-mic') {
      return 'Local recording input';
    }
    return '';
  }

  applyMeterValue(uuid, value) {
    const percent = Math.min(100, Math.max(0, value));
    this.meterValues.set(uuid, percent);
    const meter = this.rosterList.querySelector(`[data-meter="${uuid}"] .meter-bar-fill`);
    if (meter) {
      meter.style.width = `${percent}%`;
    }
  }

  updateMeterFromBus(payload) {
    if (!payload?.uuid) {
      return;
    }
    const peak = payload.peak || 0;
    const level = Math.min(100, Math.round(peak * 120));
    this.applyMeterValue(payload.uuid, level);
    this.updateTrackLevelVisual(payload.uuid, level);
  }

  updateCloudFooter() {
    if (this.driveStatusNode) {
      const driveText = this.cloud?.hasDriveAccess()
        ? 'Google Drive linked'
        : 'Drive link pending';
      this.driveStatusNode.textContent = driveText;
    }
    if (this.dropboxStatusNode) {
      const dropboxText = this.cloud?.hasDropboxAccess()
        ? 'Dropbox linked'
        : 'Dropbox link pending';
      this.dropboxStatusNode.textContent = dropboxText;
    }
    this.updateCloudLinkUI();
    this.updateReadinessSummary();
  }

  updateReadinessSummary() {
    if (this.cloudSummaryNode) {
      const state = readCloudLinkStatus();
      const driveActive = Boolean(this.cloud?.hasDriveAccess());
      const driveCached = isCloudLinkFresh(state.drive);
      const dropboxActive = Boolean(this.cloud?.hasDropboxAccess());
      const dropboxCached = isCloudLinkFresh(state.dropbox);
      const driveStatus = driveActive
        ? `Drive ready${state.drive?.linkedAt ? ` (${formatRelativeTime(state.drive.linkedAt) || 'just now'})` : ''}`
        : driveCached
          ? `Drive linked earlier (${formatRelativeTime(state.drive.linkedAt) || 'recently'}). Re-link to enable backups.`
          : 'Drive not linked';
      const dropboxStatus = dropboxActive
        ? `Dropbox ready${state.dropbox?.linkedAt ? ` (${formatRelativeTime(state.dropbox.linkedAt) || 'just now'})` : ''}`
        : dropboxCached
          ? `Dropbox linked earlier (${formatRelativeTime(state.dropbox.linkedAt) || 'recently'}). Re-link to enable backups.`
          : 'Dropbox not linked';
      this.cloudSummaryNode.textContent = `Cloud uploads: ${driveStatus} • ${dropboxStatus}`;
      this.cloudSummaryNode.dataset.state = driveActive || dropboxActive ? 'ready' : dropboxCached || driveCached ? 'error' : 'pending';
    }
    if (this.diskSummaryNode) {
      if (!STUDIO_DISK_FEATURE_FLAG) {
        this.diskSummaryNode.textContent = 'Disk capture disabled via ?studioiso=0';
        this.diskSummaryNode.dataset.state = 'disabled';
        return;
      }
      if (typeof window.showDirectoryPicker !== 'function') {
        this.diskSummaryNode.textContent = 'Disk capture: browser not supported';
        this.diskSummaryNode.dataset.state = 'disabled';
        return;
      }
      const diskState = readDiskRecordingState();
      if (diskState.lastError) {
        this.diskSummaryNode.textContent = `Disk capture: ${diskState.lastError}`;
        this.diskSummaryNode.dataset.state = 'error';
        return;
      }
      if (!diskState.folderName) {
        this.diskSummaryNode.textContent = 'Disk capture: folder not selected';
        this.diskSummaryNode.dataset.state = 'pending';
        return;
      }
      if (!diskState.enabled) {
        this.diskSummaryNode.textContent = `Disk capture: ${diskState.folderName} (toggle off)`;
        this.diskSummaryNode.dataset.state = 'idle';
        return;
      }
      const verified = diskState.lastVerifiedAt ? `checked ${formatRelativeTime(diskState.lastVerifiedAt)}` : 'awaiting verification';
      this.diskSummaryNode.textContent = `Disk capture: ${diskState.folderName} (${verified})`;
      this.diskSummaryNode.dataset.state = 'ready';
    }
  }

  formatFileSize(bytes) {
    if (!bytes && bytes !== 0) {
      return '';
    }
    const thresh = 1024;
    if (bytes < thresh) {
      return `${bytes} B`;
    }
    const units = ['KB', 'MB', 'GB', 'TB'];
    let unitIndex = -1;
    let value = bytes;
    do {
      value /= thresh;
      unitIndex += 1;
    } while (value >= thresh && unitIndex < units.length - 1);
    return `${value.toFixed(value >= 10 || unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`;
  }

  describeTrackMeta(meta) {
    const parts = [];
    if (meta?.mimeType) {
      parts.push(meta.mimeType.toUpperCase());
    }
    if (meta?.size) {
      parts.push(this.formatFileSize(meta.size));
    }
    if (meta?.durationSeconds) {
      parts.push(`${meta.durationSeconds.toFixed(1)}s`);
    }
    return parts.join(' • ');
  }

  describeService(service) {
    if (service === 'drive') {
      return 'Drive';
    }
    if (service === 'dropbox') {
      return 'Dropbox';
    }
    return service || 'Service';
  }

  createServiceStatusLine(service) {
    const line = createElement('div', 'upload-status-line');
    line.dataset.service = service;
    const ready =
      service === 'drive'
        ? this.cloud?.hasDriveAccess()
        : service === 'dropbox'
          ? this.cloud?.hasDropboxAccess()
          : false;
    const hint = ready ? 'ready' : 'link to upload';
    line.textContent = `${this.describeService(service)}: ${hint}`;
    return line;
  }

  setUploadProgressPending(pending) {
    ['drive', 'dropbox'].forEach((service) => {
      const node = this.cloudProgressNodes?.[service];
      if (!node) {
        return;
      }
      if (pending) {
        node.dataset.state = 'pending';
        node.textContent = `${this.describeService(service)} uploads pending (recording in progress)`;
      } else if (!this.uploadTrackers?.[service]?.size) {
        node.dataset.state = 'idle';
        node.textContent = `${this.describeService(service)} uploads idle`;
      }
    });
  }

  registerUploadTask(service, meta) {
    if (!service || !this.uploadTrackers?.[service]) {
      return null;
    }
    const tracker = this.uploadTrackers[service];
    const key = `${service}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
    const bytesTotal = meta?.blob?.size || 0;
    tracker.set(key, {
      key,
      label: meta?.participant?.label || meta?.filename || 'Track',
      bytesUploaded: 0,
      bytesTotal,
      status: 'pending',
      startedAt: Date.now(),
    });
    this.refreshUploadProgress(service);
    return key;
  }

  updateUploadTask(service, key, { uploaded, total, status } = {}) {
    if (!service || !key || !this.uploadTrackers?.[service]) {
      return;
    }
    const tracker = this.uploadTrackers[service];
    const entry = tracker.get(key);
    if (!entry) {
      return;
    }
    if (typeof uploaded === 'number') {
      entry.bytesUploaded = uploaded;
    }
    if (typeof total === 'number' && total >= 0) {
      entry.bytesTotal = total;
    }
    if (status) {
      entry.status = status;
    }
    this.refreshUploadProgress(service);
  }

  finalizeUploadTask(service, key, status = 'uploaded') {
    if (!service || !key || !this.uploadTrackers?.[service]) {
      return;
    }
    const tracker = this.uploadTrackers[service];
    const entry = tracker.get(key);
    if (!entry) {
      return;
    }
    entry.status = status;
    if (!entry.bytesTotal) {
      entry.bytesTotal = entry.bytesUploaded;
    }
    tracker.set(key, entry);
    this.refreshUploadProgress(service);
    const ttl = status === 'error' ? UPLOAD_TRACKER_COOLDOWN_MS * 2 : UPLOAD_TRACKER_COOLDOWN_MS;
    setTimeout(() => {
      const current = tracker.get(key);
      if (current && current.status === status) {
        tracker.delete(key);
        this.refreshUploadProgress(service);
      }
    }, ttl);
  }

  refreshUploadProgress(service) {
    const node = this.cloudProgressNodes?.[service];
    const tracker = this.uploadTrackers?.[service];
    if (!node || !tracker) {
      return;
    }
    if (!tracker.size) {
      node.textContent = `${this.describeService(service)} uploads idle`;
      node.dataset.state = 'idle';
      return;
    }
    const entries = Array.from(tracker.values());
    const errors = entries.filter((entry) => entry.status === 'error');
    const active = entries.filter((entry) => entry.status === 'pending' || entry.status === 'uploading');
    const completed = entries.filter((entry) => entry.status === 'uploaded');
    const skipped = entries.filter((entry) => entry.status === 'skipped');
    const uploadedBytes = entries.reduce((total, entry) => total + Math.min(entry.bytesUploaded || 0, entry.bytesTotal || entry.bytesUploaded || 0), 0);
    const totalBytes = entries.reduce((total, entry) => total + (entry.bytesTotal || entry.bytesUploaded || 0), 0);
    const percentage = totalBytes ? Math.min(100, Math.round((uploadedBytes / totalBytes) * 100)) : 0;
    if (errors.length) {
      node.textContent = `${this.describeService(service)} upload error (${errors.length})`;
      node.dataset.state = 'error';
      return;
    }
    if (active.length) {
      node.textContent = `${this.describeService(service)} uploading ${active.length} file${active.length === 1 ? '' : 's'} • ${percentage}%`;
      node.dataset.state = 'uploading';
      return;
    }
    if (completed.length || skipped.length) {
      node.textContent = `${this.describeService(service)} uploads complete`;
      node.dataset.state = 'complete';
      return;
    }
    node.textContent = `${this.describeService(service)} uploads idle`;
    node.dataset.state = 'idle';
  }

 applyUploadResult(element, result) {
    if (!element || !result) {
      return;
    }
    const label = this.describeService(result.service || element.dataset.service);
    element.dataset.status = result.status || 'unknown';
    if (result.status === 'uploaded') {
      const sizeText = result.bytes ? ` (${this.formatFileSize(result.bytes)})` : '';
      element.textContent = `${label}: uploaded${sizeText}`;
    } else if (result.status === 'skipped') {
      element.textContent = `${label}: ${result.reason || 'skipped'}`;
    } else if (result.status === 'error') {
      const message = result.error?.message || result.error?.toString() || 'failed';
      element.textContent = `${label}: ${message}`;
      element.dataset.status = 'error';
    } else {
      element.textContent = `${label}: ${result.status || 'unknown'}`;
    }
  }

  enqueuePendingDriveUpload(meta, driveElement) {
    if (!meta || !meta.blob) {
      return;
    }
    const existing = this.pendingDriveUploads.find((entry) => entry.meta === meta);
    if (existing) {
      if (driveElement) {
        existing.driveElement = driveElement;
      }
      return;
    }
    this.pendingDriveUploads.push({
      meta,
      driveElement: driveElement || null,
    });
    if (driveElement) {
      driveElement.dataset.status = 'pending';
      driveElement.textContent = `${this.describeService('drive')}: waiting for link…`;
    }
  }

  async flushPendingDriveUploads() {
    if (!this.pendingDriveUploads.length || !this.cloud?.hasDriveAccess()) {
      return;
    }
    const pending = [...this.pendingDriveUploads];
    this.pendingDriveUploads = [];
    for (const entry of pending) {
      try {
        await this.queueCloudUpload(
          entry.meta,
          {
            drive: entry.driveElement,
          },
          { driveOnly: true },
        );
      } catch (error) {
        console.warn('Deferred Drive upload failed', error);
      }
    }
  }

  async queueCloudUpload(meta, serviceElements = {}, options = {}) {
    if (!this.cloud || !meta?.blob) {
      if (serviceElements?.drive) {
        serviceElements.drive.textContent = 'Drive: unavailable';
      }
      if (serviceElements?.dropbox) {
        serviceElements.dropbox.textContent = 'Dropbox: unavailable';
      }
      return;
    }

    const driveOnly = options.driveOnly === true;

    let driveClient = null;
    let driveReady = false;
    try {
      driveClient = this.cloud.ensureDriveClient();
      driveReady = Boolean(this.cloud?.hasDriveAccess());
    } catch (error) {
      console.warn('Drive client unavailable; continuing without Drive uploads', error);
    }
    let canDropbox = !driveOnly && Boolean(this.cloud?.hasDropboxAccess());
    if (!driveOnly && !canDropbox) {
      try {
        const dropboxClient = await this.cloud.ensureDropboxClient();
        canDropbox = Boolean(dropboxClient);
      } catch (error) {
        console.warn('Dropbox client unavailable; continuing without Dropbox uploads', error);
      }
    }

    if (serviceElements?.drive) {
      const driveHint = driveReady ? 'preparing upload…' : 'link to upload';
      serviceElements.drive.textContent = `${this.describeService('drive')}: ${driveHint}`;
      serviceElements.drive.dataset.status = driveReady ? 'pending' : 'idle';
    }
    if (!driveOnly && serviceElements?.dropbox) {
      serviceElements.dropbox.textContent = `${this.describeService('dropbox')}: ${canDropbox ? 'preparing upload…' : 'link to upload'}`;
      serviceElements.dropbox.dataset.status = canDropbox ? 'pending' : 'idle';
    }

    const uploadKeys = {};
    const allowDriveUpload = driveReady && Boolean(driveClient);
    if (allowDriveUpload) {
      uploadKeys.drive = this.registerUploadTask('drive', meta);
    } else if (!driveOnly && driveClient) {
      this.enqueuePendingDriveUpload(meta, serviceElements?.drive || null);
    }
    if (!driveOnly && canDropbox) {
      uploadKeys.dropbox = this.registerUploadTask('dropbox', meta);
    }

    try {
      const results = await this.cloud.uploadBlob(meta.blob, {
        filename: meta.filename,
        drive: allowDriveUpload,
        dropbox: !driveOnly && canDropbox,
        onProgress: (progress) => {
          if (!progress?.service) {
            return;
          }
          const label = this.describeService(progress.service);
          if (progress.service === 'drive' && serviceElements?.drive) {
            serviceElements.drive.textContent = `${label}: ${progress.percentage || 0}%`;
            if (uploadKeys.drive) {
              this.updateUploadTask('drive', uploadKeys.drive, {
                uploaded: progress.uploaded,
                total: progress.total,
                status: 'uploading',
              });
            }
          } else if (progress.service === 'dropbox' && serviceElements?.dropbox) {
            serviceElements.dropbox.textContent = `${label}: ${progress.percentage || 0}%`;
            if (uploadKeys.dropbox) {
              this.updateUploadTask('dropbox', uploadKeys.dropbox, {
                uploaded: progress.uploaded,
                total: progress.total,
                status: 'uploading',
              });
            }
          }
        },
        signal: this.abortUploadsController?.signal,
      });
      if (allowDriveUpload) {
        this.applyUploadResult(serviceElements?.drive, results.drive);
      }
      if (!driveOnly) {
        this.applyUploadResult(serviceElements?.dropbox, results.dropbox);
      }
      if (uploadKeys.drive) {
        this.finalizeUploadTask('drive', uploadKeys.drive, results.drive?.status || 'unknown');
      }
      if (uploadKeys.dropbox) {
        this.finalizeUploadTask('dropbox', uploadKeys.dropbox, results.dropbox?.status || 'unknown');
      }
    } catch (error) {
      console.error('Cloud upload failed', error);
      if (allowDriveUpload && serviceElements?.drive) {
        serviceElements.drive.textContent = 'Drive: upload failed';
        serviceElements.drive.dataset.status = 'error';
      }
      if (!driveOnly && serviceElements?.dropbox) {
        serviceElements.dropbox.textContent = 'Dropbox: upload failed';
        serviceElements.dropbox.dataset.status = 'error';
      }
      if (uploadKeys.drive) {
        this.finalizeUploadTask('drive', uploadKeys.drive, 'error');
      }
      if (uploadKeys.dropbox) {
        this.finalizeUploadTask('dropbox', uploadKeys.dropbox, 'error');
      }
    } finally {
      this.updateCloudFooter();
    }
  }

  cleanupDownloadUrls() {
    if (!this.activeDownloadUrls || !this.activeDownloadUrls.length) {
      return;
    }
    this.activeDownloadUrls.forEach((url) => {
      try {
        URL.revokeObjectURL(url);
      } catch (error) {
        console.warn('Failed to revoke object URL', error);
      }
    });
    this.activeDownloadUrls = [];
  }

  dispose() {
    if (this.rosterTimer) {
      clearInterval(this.rosterTimer);
      this.rosterTimer = null;
    }
    if (this.diskStateListener) {
      window.removeEventListener(PODCAST_DISK_EVENT, this.diskStateListener);
      this.diskStateListener = null;
    }
    if (this.cloudStateListener) {
      window.removeEventListener(PODCAST_CLOUD_EVENT, this.cloudStateListener);
      this.cloudStateListener = null;
    }
    if (this.boundDriveProgressHandler) {
      window.removeEventListener(DRIVE_PROGRESS_EVENT, this.boundDriveProgressHandler);
      this.boundDriveProgressHandler = null;
    }
    if (this.levelOff) {
      this.levelOff();
      this.levelOff = null;
    }
    if (this.abortUploadsController) {
      this.abortUploadsController.abort();
      this.abortUploadsController = null;
    }
    if (this.hostMic?.active || this.virtualParticipants.size) {
      this.disableHostMic().catch((error) => {
        console.warn('Failed to disable host microphone during dispose', error);
      });
    }
    this.restoreRemoteControls();
    if (this.chatModule) {
      try {
        if (this.chatModule.dataset) {
          delete this.chatModule.dataset.podcastOverlay;
        }
        if (this.chatPlaceholder?.parentNode) {
          this.chatPlaceholder.parentNode.insertBefore(this.chatModule, this.chatPlaceholder);
          this.chatPlaceholder.parentNode.removeChild(this.chatPlaceholder);
        }
        const legacyHeader = this.chatModule.querySelector('.chat-header');
        if (legacyHeader) {
          if (legacyHeader.dataset && Object.prototype.hasOwnProperty.call(legacyHeader.dataset, 'podcastDisplay')) {
            legacyHeader.style.display = legacyHeader.dataset.podcastDisplay || '';
            delete legacyHeader.dataset.podcastDisplay;
          } else {
            legacyHeader.style.display = '';
          }
        }
        const legacyResizer = this.chatModule.querySelector('.resizer');
        if (legacyResizer) {
          if (legacyResizer.dataset && Object.prototype.hasOwnProperty.call(legacyResizer.dataset, 'podcastDisplay')) {
            legacyResizer.style.display = legacyResizer.dataset.podcastDisplay || '';
            delete legacyResizer.dataset.podcastDisplay;
          } else {
            legacyResizer.style.display = '';
          }
        }
        const popLink = this.chatModule.querySelector('#popOutChat');
        if (popLink) {
          popLink.style.display = '';
        }
        const closeLink = this.chatModule.querySelector('#closeChat');
        if (closeLink) {
          closeLink.style.display = '';
        }
        if (this.chatModule.style) {
          this.chatModule.style.position = '';
          this.chatModule.style.right = '';
          this.chatModule.style.left = '';
          this.chatModule.style.bottom = '';
          this.chatModule.style.top = '';
          this.chatModule.style.zIndex = '';
          this.chatModule.style.maxWidth = '';
          this.chatModule.style.width = '';
          this.chatModule.style.height = '';
          this.chatModule.style.maxHeight = '';
          this.chatModule.style.overflow = '';
          this.chatModule.style.margin = '';
        }
        this.chatModule.classList.add('hidden');
      } catch (error) {
        console.warn('Failed to restore chat module', error);
      }
      this.chatModule = null;
      this.chatPlaceholder = null;
    }
    this.chatPanel = null;
    this.chatCollapseButton = null;
    this.chatPopoutButton = null;
    this.chatPopoutAnchor = null;
    this.chatCollapsed = false;
    this.chatCollapsedHint = null;
    this.cleanupDownloadUrls();
    if (this.stopMeterBridge) {
      this.stopMeterBridge();
      this.stopMeterBridge = null;
    }
    if (this.inviteCopyTimer) {
      clearTimeout(this.inviteCopyTimer);
      this.inviteCopyTimer = null;
    }
    if (this.remoteOverlay && this.remoteOverlay.parentNode) {
      this.remoteOverlay.parentNode.removeChild(this.remoteOverlay);
    }
    this.remoteOverlay = null;
    this.remoteOverlayContent = null;
    this.rosterDriveButtons.clear();
    this.rosterDriveStatuses.clear();
    this.driveStatusResetTimers.forEach((timer) => clearTimeout(timer));
    this.driveStatusResetTimers.clear();
  }
}

async function bootstrap() {
  try {
    const preflight = await ensureRoomSelection();
    if (preflight?.redirect) {
      return;
    }
    const app = new PodcastStudioApp({ roomHint: preflight?.roomSlug });
    await app.init();
    window.podcastStudioApp = app;
  } catch (error) {
    console.error('Failed to initialise podcast studio', error);
  }
}

bootstrap();
